ui/src/main/java/net/crofis/ui/custom/actionitem/ActionItemClickListener.java
public interface ActionItemClickListener {
    void onActionSelected();
}
ui/src/main/java/net/crofis/ui/dialog/InfoDialog.java
public class InfoDialog extends BaseAlertDialog{

    private static final String TAG = "InfoDialog.class";

    /**Title of the dialog**/
    private TextView title;

    /**Message of the dialog**/
    private TextView message;

    /**Profile Image**/
    private CircleImageView icon;

    /**Positive Button**/
    private FloatingActionButton postiveButton;

    /**Natural Button**/
    private FloatingActionButton naturalButton;

    /**Negative Button**/
    private FloatingActionButton negativeButton;

    private View.OnClickListener PositiveListener;

    private View.OnClickListener NegativeListener;

    private View.OnClickListener NaturalListener;

    /**An image that is set on the image**/
    private Bitmap imageBitmap;
    private Drawable imageDrawable;

    /**In charge of the slide in animation**/
    private boolean allowAnimation = true;

    /**Is dialog cancelable**/
    private boolean cancelable = true;

    private boolean isLocked = false;

    /**Delay of button animation to start**/
    private static final int startControlAnimationDelay = 300;

    /**Delay between each button**/
    private static final int gapControlAnimationDelay = 150;


    public InfoDialog(Context context){
        this.context = context;
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this.context);
        LayoutInflater inflater = LayoutInflater.from(this.context);
        dialogView = inflater.inflate(R.layout.ui_simple_dialog, null);
        this.title = (TextView) dialogView.findViewById(R.id.dialog_title);
        this.title.setText("");
        this.message= (TextView) dialogView.findViewById(R.id.dialog_message);
        this.message.setMovementMethod(new ScrollingMovementMethod());
        this.icon = (CircleImageView) dialogView.findViewById(R.id.dialog_imageView);
        this.postiveButton = (FloatingActionButton) dialogView.findViewById(R.id.pos);
        this.negativeButton = (FloatingActionButton) dialogView.findViewById(R.id.neg);
        this.naturalButton = (FloatingActionButton) dialogView.findViewById(R.id.natural);
        dialogView.findViewById(R.id.background).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });
        dialogBuilder.setView(dialogView);
        dialog = dialogBuilder.create();
        this.dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        if (allowAnimation)
            dialog.getWindow().getAttributes().windowAnimations = R.style.customDialogAnimation;
    }

    /**
     * Show the dialog.
     */
    @Override
    public void show(){
        if(!isLocked) {


            View.OnClickListener dismissDialog = new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    dismiss();
                }
            };
            if (title.getText().length() == 0) title.setVisibility(View.GONE);
            if (this.imageBitmap == null && this.imageDrawable == null)
                icon.setVisibility(View.GONE);
            if (NaturalListener == null) this.naturalButton.setVisibility(View.GONE);
            if (this.cancelable) {
                if (NegativeListener == null && PositiveListener == null) {
                    PositiveListener = dismissDialog;
                    postiveButton.setOnClickListener(dismissDialog);


                    negativeButton.setVisibility(View.GONE);
                } else {
                    if (PositiveListener != null && NegativeListener == null) {
                        negativeButton.setOnClickListener(dismissDialog);
                    }
                }
            } else {
                if (NegativeListener == null && PositiveListener == null) {
                    negativeButton.setVisibility(View.GONE);
                    postiveButton.setVisibility(View.GONE);
                }
            }

            if(allowAnimation) {
                postiveButton.hide(false);
                negativeButton.hide(false);
                naturalButton.hide(false);
                int delay = startControlAnimationDelay;
                Handler handler = new Handler();
                for (int i = 0; i < 3; i++) {
                    final int finalI = i;

                    handler.postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            switch (finalI) {
                                case 0:
                                    if (PositiveListener != null) postiveButton.show(true);
                                    break;
                                case 1:
                                    if (NegativeListener != null) negativeButton.show(true);
                                    break;
                                case 2:
                                    if (NaturalListener != null) naturalButton.show(true);
                                    break;
                            }


                        }
                    }, delay);
                    delay += gapControlAnimationDelay;
                    Log.i(TAG, delay + " " + i);
                }
            }


            dialog.setCancelable(this.cancelable);
            dialog.show();
        }
        else Log.e(TAG,"Dialog is locked.");
    }

    /**
     * Dismiss the dialog.
     */
    @Override
    public void dismiss(){
        if(!isLocked) dialog.dismiss();
        else Log.e(TAG,"Dialog is locked.");
    }

    @Override
    public AlertDialog getDialog() {
        return dialog;
    }

    /**
     * Set a on click listener for the positive button.
     * @param listener is the positive listener.
     */

    public void setPostiveButtonOnClickListener(final OnClickListener listener){
        this.PositiveListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.onClick(v,InfoDialog.this);
            }
        };
        this.postiveButton.setOnClickListener(PositiveListener);
    }

    /**
     * Set a on click listener for the negative button.
     * @param listener is the negative listener
     */
    public  void setNegativeButtonOnClickListener(final OnClickListener listener){
        this.NegativeListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.onClick(v,InfoDialog.this);
            }
        };
        this.negativeButton.setOnClickListener(NegativeListener);
    }

    public void setNaturalButtonOnClickListener(final OnClickListener listener){
        this.NaturalListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.onClick(v,InfoDialog.this);
            }
        };
        this.naturalButton.setOnClickListener(NaturalListener);
    }

    public void setOnDismissListener(DialogInterface.OnDismissListener dismissListener){
        this.dialog.setOnDismissListener(dismissListener);
    }

    public AlertDialog getRootDialog(){
        return this.dialog;
    }

    /**
     * Set the title of the dialog.
     * @param title will be shown on the Title.
     */
    public void setTitle(String title){
        this.title.setText(title);
    }

    /**
     * Set the message of the dialog.
     * @param message will be shown on the Message.
     */
    public void setMessage(String message){
        this.message.setText(message);
    }

    /**
     * Set circular image in the corner of the dialog.
     * @param bitmap is the image that will be shown.
     */
    public void setBitmapIcon(Bitmap bitmap){
        this.imageBitmap = bitmap;
        this.icon.setImageBitmap(bitmap);
    }

    /**
     * Set circular image in the corner of the dialog.
     * @param drawable is the image that will be shown.
     */

    public void setDrawableIcon(Drawable drawable){
        this.imageDrawable = drawable;
        this.icon.setImageDrawable(drawable);
    }

    /**
     * Set the animation.
     * @param show - true means the dialog will be displayed with a slide in/out animation. If set to false, the default fade will take place.
     */

    public void showAnimation(boolean show){
        this.allowAnimation = show;
    }

    /**
     * Set the dialog dismiss-by-user state.
     * @param isCancelable if true dialog can be dismissed by the user, if false dialog can only be dismissed by calling Dialog.dismiss().
     */
    public void setCancelable(boolean isCancelable){
        this.cancelable = isCancelable;
    }


    public boolean isLocked() {
        return isLocked;
    }

    public void setLock(boolean isLocked) {
        this.isLocked = isLocked;
    }

    public TextView getTitle() {
        return title;
    }

    public TextView getMessage() {
        return message;
    }

    public CircleImageView getIcon() {
        return icon;
    }

    public FloatingActionButton getPostiveButton() {
        return postiveButton;
    }

    public FloatingActionButton getNegativeButton() {
        return negativeButton;
    }

    public FloatingActionButton getNaturalButton() {
        return naturalButton;
    }

    @Override
    public View getDialogView() {
        return dialogView;
    }

    @Override
    public Context getContext() {
        return context;
    }
}
ui/src/main/java/net/crofis/ui/custom/seekbar/SeekBarCompat.java
public class SeekBarCompat extends SeekBar implements View.OnTouchListener {

    private static final String TAG = "SeekBarCompat";
    private int mActualBackgroundColor;

    /***
     * Thumb and Progress colors
     */
    int mThumbColor, mProgressColor, mProgressBackgroundColor;

    /***
     * Thumb drawable
     */
    Drawable mThumb;
    /***
     * States for Lollipop ColorStateList
     */
    int[][] states = new int[][]{
            new int[]{android.R.attr.state_enabled}, // enabled
            new int[]{android.R.attr.state_pressed},  // pressed
            new int[]{-android.R.attr.state_enabled}, // disabled
            new int[]{} //everything else
    };

    /***
     * Default colors to be black for Thumb ColorStateList
     */
    int[] colorsThumb = new int[]{
            Color.BLACK,
            Color.BLACK,
            Color.LTGRAY,
            Color.BLACK
    };

    /***
     * Default colors to be black for Progress ColorStateList
     */
    int[] colorsProgress = new int[]{
            Color.BLACK,
            Color.BLACK,
            Color.LTGRAY,
            Color.BLACK
    };

    /***
     * Default colors to be black for Progress ColorStateList
     */
    int[] colorsProgressBackground = new int[]{
            Color.BLACK,
            Color.BLACK,
            Color.LTGRAY,
            Color.BLACK

    };

    /***
     * ColorStateList objects
     */
    ColorStateList mColorStateListThumb, mColorStateListProgress, mColorStateListProgressBackground;

    /***
     * Used for APIs below 21 to determine height of the seekBar as well as the new thumb drawable
     */
    private int mOriginalThumbHeight;
    private int mThumbAlpha = 255;
    private boolean mIsEnabled = true;

    /***
     * Updates the thumbColor dynamically
     *
     * @param thumbColor Color representing thumb drawable
     */
    public void setThumbColor(final int thumbColor) {
        mThumbColor = thumbColor;
        if (lollipopAndAbove()) {
            setupThumbColorLollipop();
        } else {
            gradientDrawable.setColor(mIsEnabled ? thumbColor : Color.LTGRAY);
        }
        invalidate();
        requestLayout();
    }

    /***
     * Method called for APIs 21 and above to setup thumb Color
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void setupThumbColorLollipop() {
        if (lollipopAndAbove()) {
            colorsThumb[0] = mThumbColor;
            colorsThumb[1] = mThumbColor;
            colorsThumb[2] = Color.LTGRAY;
            mColorStateListThumb = new ColorStateList(states, colorsThumb);
            setThumbTintList(mColorStateListThumb);
        } else {

        }
    }

    /***
     * Updates the progressColor dynamically
     *
     * @param progressColor Color representing progress drawable
     */
    public void setProgressColor(final int progressColor) {
        mProgressColor = progressColor;
        if (lollipopAndAbove()) {
            setupProgressColorLollipop();
        } else {
            setupProgressColor();
        }

        invalidate();
        requestLayout();
    }

    /***
     * Checks if the device is running API greater than 21
     *
     * @return true if lollipop and above
     */
    private boolean lollipopAndAbove() {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
    }

    /***
     * Method called from APIs below 21 to setup Progress Color
     */
    private void setupProgressColor() {
        try {
            //load up the drawable and apply color
            LayerDrawable ld = (LayerDrawable) getProgressDrawable();
            ScaleDrawable shape = (ScaleDrawable) (ld.findDrawableByLayerId(android.R.id.progress));
            shape.setColorFilter(mProgressColor, PorterDuff.Mode.SRC_IN);

            //set the background to transparent
            NinePatchDrawable ninePatchDrawable = (NinePatchDrawable) (ld.findDrawableByLayerId(android.R.id.background));
            ninePatchDrawable.setColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_IN);
        } catch (NullPointerException e) {
            //TODO: Handle exception
        }
    }

    /***
     * Method called from APIs >= 21 to setup Progress Color
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void setupProgressColorLollipop() {
        colorsProgress[0] = mProgressColor;
        colorsProgress[1] = mProgressColor;
        mColorStateListProgress = new ColorStateList(states, colorsProgress);
        setProgressTintList(mColorStateListProgress);
    }

    /***
     * Updates the progressBackgroundColor dynamically
     *
     * @param progressBackgroundColor Color representing progress drawable
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public void setProgressBackgroundColor(final int progressBackgroundColor) {
        mProgressBackgroundColor = progressBackgroundColor;
        if (lollipopAndAbove()) {
            setupProgressBackgroundLollipop();
        } else {
            setupProgressBackground();
        }
        invalidate();
        requestLayout();
    }

    /***
     * Method called from APIs 21 and above to setup the Progress-background-line Color
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void setupProgressBackgroundLollipop() {
        colorsProgressBackground[0] = mProgressBackgroundColor;
        colorsProgressBackground[1] = mProgressBackgroundColor;
        mColorStateListProgressBackground = new ColorStateList(states, colorsProgressBackground);
        setProgressBackgroundTintList(mColorStateListProgressBackground);
    }

    /***
     * Method called from APIs below 21 to setup the Progress-background-line Color
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void setupProgressBackground() {
        //load up the drawable and apply color
        SeekBarBackgroundDrawable seekBarBackgroundDrawable = new SeekBarBackgroundDrawable(getContext(),
                mProgressBackgroundColor, mActualBackgroundColor, getPaddingLeft(), getPaddingRight());
        if (belowJellybean())
            setBackgroundDrawable(seekBarBackgroundDrawable);
        else
            setBackground(seekBarBackgroundDrawable);
    }

    /***
     * Constructor for creating SeekBarCompat through code
     *
     * @param context Context object
     */
    public SeekBarCompat(final Context context) {
        super(context);
    }

    GradientDrawable gradientDrawable = new GradientDrawable();

    /***
     * Constructor for creating SeekBarCompat through XML
     *
     * @param context Context Object
     * @param attrs   Attributes passed through XML
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public SeekBarCompat(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = context.getTheme()
                .obtainStyledAttributes(
                        attrs,
                        R.styleable.SeekBarCompat,
                        0, 0);
        int array[] = {android.R.attr.background, android.R.attr.enabled};
        TypedArray b = context.getTheme()
                .obtainStyledAttributes(attrs, array, 0, 0);
        try {
            mThumbColor = a.getColor(R.styleable.SeekBarCompat_thumbColor, getPrimaryColorFromSelectedTheme(context));
            mProgressColor = a.getColor(R.styleable.SeekBarCompat_progressColor, getPrimaryColorFromSelectedTheme(context));
            mProgressBackgroundColor = a.getColor(R.styleable.SeekBarCompat_progressBackgroundColor, Color.BLACK);
            mThumbAlpha = (int) (a.getFloat(R.styleable.SeekBarCompat_thumbAlpha, 1) * 255);
            mActualBackgroundColor = b.getColor(0, Color.TRANSPARENT);
            mIsEnabled = b.getBoolean(1, true);
            if (lollipopAndAbove()) {
                setSplitTrack(false);
                setupThumbColorLollipop();
                setupProgressColorLollipop();
                setupProgressBackgroundLollipop();
                getThumb().setAlpha(mThumbAlpha);
            } else {
                Log.e(TAG, "SeekBarCompat isEnabled? " + mIsEnabled);
                setupProgressColor();
                setOnTouchListener(this);
                gradientDrawable.setShape(GradientDrawable.OVAL);
                gradientDrawable.setSize(50, 50);
                gradientDrawable.setColor(mIsEnabled ? mThumbColor : Color.LTGRAY);
                triggerMethodOnceViewIsDisplayed(this, new Callable<Void>() {
                    @Override
                    public Void call() throws Exception {
                        ViewGroup.LayoutParams layoutParams = getLayoutParams();
                        mOriginalThumbHeight = mThumb.getIntrinsicHeight();
                        gradientDrawable.setSize(mOriginalThumbHeight / 3, mOriginalThumbHeight / 3);
                        gradientDrawable.setAlpha(mThumbAlpha);
                        setThumb(gradientDrawable);
                        if (layoutParams.height < mOriginalThumbHeight)
                            layoutParams.height = mOriginalThumbHeight;
                        setupProgressBackground();
                        return null;
                    }
                });
            }
        } finally {
            a.recycle();
            b.recycle();
        }
    }

    private boolean belowJellybean() {
        return Build.VERSION.SDK_INT < 16;
    }

    /***
     * Gets the Primary Color from theme
     *
     * @param context Context Object
     * @return Primary Color
     */
    public static int getPrimaryColorFromSelectedTheme(Context context) {
        int[] attrs = {R.attr.colorPrimary, R.attr.colorPrimaryDark};
        TypedArray ta = context.getTheme()
                .obtainStyledAttributes(attrs);
        int primaryColor = ta.getColor(0, Color.BLACK); //1 index for primaryColorDark
        //default value for primaryColor is set to black if primaryColor not found
        ta.recycle();
        return primaryColor;
    }

    /***
     * Utility method for ViewTreeObserver
     *
     * @param view   View object
     * @param method Method to be called once View is displayed
     */
    public static void triggerMethodOnceViewIsDisplayed(final View view, final Callable<Void> method) {
        final ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (Build.VERSION.SDK_INT < 16) {
                    view.getViewTreeObserver()
                            .removeGlobalOnLayoutListener(this);
                } else view.getViewTreeObserver()
                        .removeOnGlobalLayoutListener(this);
                try {
                    method.call();
                } catch (Exception e) {
                    Log.e(TAG, "onGlobalLayout " + e.toString());
                }
            }
        });
    }

    /***
     * Touch listener for changing Thumb Drawable
     *
     * @param v     View Object
     * @param event Motion Event
     * @return
     */
    @Override
    public boolean onTouch(final View v, final MotionEvent event) {
        if (!lollipopAndAbove())
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    gradientDrawable = new GradientDrawable();
                    gradientDrawable.setShape(GradientDrawable.OVAL);
                    gradientDrawable.setSize(mOriginalThumbHeight / 2, mOriginalThumbHeight / 2);
                    gradientDrawable.setColor(mIsEnabled ? mThumbColor : Color.LTGRAY);
                    gradientDrawable.setDither(true);
                    gradientDrawable.setAlpha(mThumbAlpha);
                    setThumb(gradientDrawable);
                    break;
                case MotionEvent.ACTION_UP:
                    gradientDrawable = new GradientDrawable();
                    gradientDrawable.setShape(GradientDrawable.OVAL);
                    gradientDrawable.setSize(mOriginalThumbHeight / 3, mOriginalThumbHeight / 3);
                    gradientDrawable.setColor(mIsEnabled ? mThumbColor : Color.LTGRAY);
                    gradientDrawable.setDither(true);
                    gradientDrawable.setAlpha(mThumbAlpha);
                    setThumb(gradientDrawable);
                    break;
            }
        return false;
    }


    /***
     * Called to substitute getThumb() for APIs below 16
     *
     * @param thumb
     */
    @Override
    public void setThumb(final Drawable thumb) {
        super.setThumb(thumb);
        mThumb = thumb;
    }

    /***
     * Sets the thumb alpha (Obviously)
     *
     * @param alpha
     */
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    public void setThumbAlpha(@IntRange(from = 0, to = 255) int alpha) {
        mThumbAlpha = alpha;
        if (!belowJellybean())
            getThumb().setAlpha(mThumbAlpha);
        setLayoutParams(getLayoutParams());
    }

    /***
     * Enables or disables the whole seekBar!
     *
     * @param enabled
     */
    @Override
    public void setEnabled(final boolean enabled) {
        mIsEnabled = enabled;
        triggerMethodOnceViewIsDisplayed(this, new Callable<Void>() {
            @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
            @Override
            public Void call() throws Exception {
                if (!lollipopAndAbove()) {
                    gradientDrawable = new GradientDrawable();
                    gradientDrawable.setShape(GradientDrawable.OVAL);
                    gradientDrawable.setSize(mOriginalThumbHeight / 3, mOriginalThumbHeight / 3);
                    gradientDrawable.setColor(mIsEnabled ? mThumbColor : Color.LTGRAY);
                    gradientDrawable.setDither(true);
                    gradientDrawable.setAlpha(mThumbAlpha);
                    setThumb(gradientDrawable);
                    //load up the drawable and apply color
                    LayerDrawable ld = (LayerDrawable) getProgressDrawable();
                    ScaleDrawable shape = (ScaleDrawable) (ld.findDrawableByLayerId(android.R.id.progress));
                    shape.setColorFilter(mIsEnabled ? mProgressColor : Color.LTGRAY, PorterDuff.Mode.SRC_IN);
                    //set the background to transparent
                    NinePatchDrawable ninePatchDrawable = (NinePatchDrawable) (ld.findDrawableByLayerId(android.R.id.background));
                    ninePatchDrawable.setColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_IN);
                    //background
                    //load up the drawable and apply color
                    SeekBarBackgroundDrawable seekBarBackgroundDrawable = new SeekBarBackgroundDrawable(getContext(),
                            mIsEnabled ? mProgressBackgroundColor : Color.LTGRAY, mActualBackgroundColor, getPaddingLeft(), getPaddingRight());
                    if (belowJellybean())
                        setBackgroundDrawable(seekBarBackgroundDrawable);
                    else
                        setBackground(seekBarBackgroundDrawable);
                }
                SeekBarCompat.super.setEnabled(enabled);
                return null;
            }
        });

    }
}
ui/src/main/java/net/crofis/ui/dialog/DialogManager.java
public final class DialogManager {

    private static final String TAG = "DialogManager";


    /**
     *
     * @param context context of the app.
     * @return returns a NewMessageDialog.
     */
    public static NewMessageDialog makeMessageDialog(Context context)
    {
        NewMessageDialog dialog = new NewMessageDialog(context);
        return dialog;
    }

    /**
     *
     * @param context context of the app.
     * @param title  title of the dialog.
     * @return returns a NewMessageDialog.
     */
    public static NewMessageDialog makeMessageDialog(Context context, String title){
        NewMessageDialog dialog = makeMessageDialog(context);
        dialog.setTitle(title);
        return dialog;
    }

    /**
     *
     * @param context context of the app.
     * @param title  title of the dialog.
     * @param image image of the dialog.
     * @return returns a NewMessageDialog.
     */

    public static NewMessageDialog makeMessageDialog(Context context, String title, Bitmap image){
        NewMessageDialog dialog = makeMessageDialog(context,title);
        dialog.setImageBitmap(image);
        return dialog;
    }

    public static NewMessageDialog makeMessageDialog(Context context, String title, boolean UseCamera){
        NewMessageDialog dialog = makeMessageDialog(context,title);
        dialog.setAllowCameraButton(UseCamera);
        return dialog;
    }



    /**
     * returns a read dialog.
     * @param context context of the app.
     * @return
     */
    public static InfoDialog makeReadDialog(Context context){
        InfoDialog dialog = new InfoDialog(context);
        dialog.getPostiveButton().setImageDrawable(context.getResources().getDrawable(R.drawable.ic_thumb_up_white_24dp));
        dialog.getNegativeButton().setImageDrawable(context.getResources().getDrawable(R.drawable.ic_thumb_down_white_24dp));
        dialog.getNaturalButton().setImageDrawable(context.getResources().getDrawable(R.drawable.ic_message_white_24dp));
        return dialog;
    }

    /**
     *
     * @param context context of the app.
     * @param title title of the dialog.
     * @param message message of the dialog.
     * @return returns a read-dialog.
     */
    public static InfoDialog makeReadDialog(Context context,String title,String message){
        InfoDialog dialog = makeReadDialog(context);
        dialog.setTitle(title);
        dialog.setMessage(message);
        return dialog;
    }

    /**
     *
     * @param context context of the app.
     * @param title title of the dialog.
     * @param message message of the dialog.
     * @param icon icon of the dialog (bitmap).
     * @return returns a read-dialog.
     */

    public static InfoDialog makeReadDialog(Context context,String title,String message,Bitmap icon){
        InfoDialog dialog = makeReadDialog(context,title,message);
        dialog.setBitmapIcon(icon);
        return dialog;
    }

    /**
     *
     * @param context context of the app.
     * @param title title of the dialog.
     * @param message message of the dialog.
     * @param icon icon of the app (drawable).
     * @return returns a read-dialog.
     */

    public static InfoDialog makeReadDialog(Context context,String title,String message,Drawable icon){
        InfoDialog dialog = makeReadDialog(context,title,message);
        dialog.setDrawableIcon(icon);
        return dialog;
    }

    /**
     *
     * @param context context of the app.
     * @param title title of the dialog.
     * @param message message of the dialog.
     * @param icon icon of the app (drawable).
     * @param animation fade-in fade-out animation.
     * @return returns a read-dialog.
     */

    public static InfoDialog makeReadDialog(Context context,String title,String message,Drawable icon,boolean animation){
        InfoDialog dialog = makeReadDialog(context,title,message,icon);
        dialog.showAnimation(animation);
        return dialog;
    }

    /**
     *
     * @param context context of the app.
     * @param title title of the dialog.
     * @param message message of the dialog.
     * @param icon icon of the app (bitmap).
     * @param animation fade-in fade-out animation.
     * @return returns a read-dialog.
     */

    public static InfoDialog makeReadDialog(Context context,String title,String message,Bitmap icon,boolean animation){
        InfoDialog dialog = makeReadDialog(context,title,message,icon);
        dialog.showAnimation(animation);
        return dialog;
    }

    /**
     *
     * @param context Context of the app.
     * @param title Title of the dialog.
     * @param message Message of the dialog.
     * @return returns InfoDialog.
     */
    public static InfoDialog makeDialog(Context context,String title,String message){
        InfoDialog infoDialog = new InfoDialog(context);
        infoDialog.setTitle(title);
        infoDialog.setMessage(message);
        return infoDialog;
    }

    /**
     *
     * @param context Context of the app.
     * @param title Title of the dialog.
     * @param message Message of the dialog.
     * @param icon icon of the dialog (Bitmap).
     * @return returns InfoDialog.
     */

    public static InfoDialog makeDialog(Context context,String title,String message,Bitmap icon){
        InfoDialog infoDialog = makeDialog(context, title, message);
        infoDialog.setBitmapIcon(icon);
        return infoDialog;
    }

    /**
     *
     * @param context Context of the app.
     * @param title Title of the dialog.
     * @param message Message of the dialog.
     * @param icon icon of the dialog (Drawable).
     * @return returns InfoDialog.
     */
    public static InfoDialog makeDialog(Context context,String title,String message,Drawable icon){
        InfoDialog infoDialog = makeDialog(context, title, message);
        infoDialog.setDrawableIcon(icon);
        return infoDialog;
    }

    /**
     *
     * @param context Context of the app.
     * @param title Title of the dialog.
     * @param message Message of the dialog.
     * @param icon icon of the dialog (Bitmap).
     * @param animation show with/without animation.
     * @return returns InfoDialog.
     */
    public static InfoDialog makeDialog(Context context,String title,String message,Bitmap icon,boolean animation){
        InfoDialog infoDialog = makeDialog(context,title,message,icon);
        infoDialog.showAnimation(animation);
        return infoDialog;
    }

    /**
     *
     * @param context Context of the app.
     * @param title Title of the dialog.
     * @param message Message of the dialog.
     * @param icon icon of the dialog (Drawable).
     * @param animation show with/without animation.
     * @return returns InfoDialog.
     */
    public static InfoDialog makeDialog(Context context,String title,String message,Drawable icon,boolean animation){
        InfoDialog infoDialog = makeDialog(context, title, message,icon);
        infoDialog.showAnimation(animation);
        return infoDialog;
    }


    /**
     *
     * @param context - Application context.
     * @param actionList - An Array List that contains the names of the actions, the icons, and the runnables
     * @param title - The title of the dialog.
     * @return ActionDialog
     */
    public static ActionDialog makeActionDialog(Context context, ArrayList<ActionItem> actionList, String title){
        ActionDialog dialog = new ActionDialog(context,actionList);
        dialog.getTitle().setText(title);
        return dialog;
    }

    /**
     *
     * @param context - Application context.
     * @param actionList - An Array List that contains the names of the actions, the icons, and the runnables
     * @param title - The title of the dialog.
     * @param showTitle - true to display the title, false to hide it.
     * @return ActionDialog
     */
    public static ActionDialog makeActionDialog(Context context, ArrayList<ActionItem> actionList,String title,boolean showTitle){
        ActionDialog dialog = new ActionDialog(context,actionList);
        if(showTitle) dialog.getTitle().setVisibility(View.VISIBLE);
        else dialog.getTitle().setVisibility(View.GONE);
        return dialog;
    }

    /**
     *
     * @param context - Application context.
     * @param actionList - An Array List that contains the names of the actions, the icons, and the runnables
     * @param title - The title of the dialog.
     * @param showTitle - true to display the title, false to hide it.
     * @param showIcons - true to show icons, false to hide them.
     * @return ActionDialog
     */
    public static ActionDialog makeActionDialog(Context context, ArrayList<ActionItem> actionList,String title,boolean showTitle,boolean showIcons){
        ActionDialog dialog = makeActionDialog(context, actionList,title, showTitle);
        dialog.setShowActionIcons(showIcons);
        return dialog;
    }


    /**
     * Set the position of the dialog. only supports ActionDialog, InfoDialog,LoadingDialog.
     *
     * @param parent The Dialog Object.
     * @param dialog use dialog.getAlertDialog()
     * @param gravity The requested gravity (only top or bottom or center)
     */
    @Deprecated
    public static void setDialogPosition(BaseAlertDialog parent,Dialog dialog,int gravity){

        if(parent instanceof ActionDialog||parent instanceof InfoDialog||parent instanceof LoadingDialog){
            WindowManager.LayoutParams wlp = dialog.getWindow().getAttributes();

            if(gravity == TOP ||gravity == BOTTOM||gravity == CENTER) wlp.gravity = gravity;
            else {
                Log.e(TAG, "setDialogPosition: Gravity type not supported! only Gravity.TOP or Gravity.BOTTOM.");
                return;
            }

            //wlp.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            wlp.windowAnimations = R.style.bottomDialogAnimation;
            switch (gravity){
                case BOTTOM:
                    wlp.windowAnimations = R.style.bottomDialogAnimation;
                    break;
                case TOP:
                    wlp.windowAnimations = R.style.topDialogAnimation;
                    break;
                default:
                    wlp.windowAnimations = R.style.customDialogAnimation;
                    break;
            }
            dialog.getWindow().setAttributes(wlp);

            if(parent instanceof ActionDialog){
                ActionDialog dialog1 = (ActionDialog)parent;
                FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)dialog1.getDialogView().findViewById(R.id.dialogFrame).getLayoutParams();

                int topMargin = params.topMargin;
                int leftMargin = params.leftMargin;
                int rightMargin = params.rightMargin;
                int bottomMargin = params.bottomMargin;
                switch (gravity){
                    case BOTTOM:
                        bottomMargin/=3;
                        break;
                    case TOP:
                        topMargin/=3;
                        break;
                }

                params.setMargins(leftMargin,topMargin,rightMargin,bottomMargin);
                dialog1.getDialogView().setLayoutParams(params);
                return;
            }
            if(parent instanceof InfoDialog){
                InfoDialog dialog1 = (InfoDialog)parent;
                LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)dialog1.getDialogView().getLayoutParams();
                int topMargin = params.topMargin;
                int leftMargin = params.leftMargin;
                int rightMargin = params.rightMargin;
                int bottomMargin = params.bottomMargin;
                switch (gravity){
                    case BOTTOM:
                        bottomMargin/=3;
                        break;
                    case TOP:
                        topMargin/=3;
                        break;
                }

                params.setMargins(leftMargin,topMargin,rightMargin,bottomMargin);
                dialog1.getDialogView().setLayoutParams(params);
                return;
            }
            if(parent instanceof LoadingDialog){
                InfoDialog dialog1 = (InfoDialog)parent;
                LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)dialog1.getDialogView().getLayoutParams();
                int topMargin = params.topMargin;
                int leftMargin = params.leftMargin;
                int rightMargin = params.rightMargin;
                int bottomMargin = params.bottomMargin;
                switch (gravity){
                    case BOTTOM:
                        bottomMargin/=3;
                        break;
                    case TOP:
                        topMargin/=3;
                        break;
                }

                params.setMargins(leftMargin,topMargin,rightMargin,bottomMargin);
                dialog1.getDialogView().setLayoutParams(params);
                return;
            }
        }
        else {
            Log.e(TAG, "setDialogPosition: Dialog type not supported! Supported dialogs: ActionDialog, InfoDialog, LoadingDialog.");
            return;
        }
    }

    /**
     * Set the position of the dialog. Support every sub class of BaseAlertDialog.
     *
     * @param parent Sub class of BaseAlertDialog.
     * @param gravity The requested position. Gravity.TOP, Gravity.BOTTOM
     */
    public static void setDialogPostion(BaseAlertDialog parent,int gravity){
        AlertDialog dialog = parent.getDialog();
        WindowManager.LayoutParams wlp = dialog.getWindow().getAttributes();

        if(gravity == TOP ||gravity == BOTTOM||gravity == CENTER) wlp.gravity = gravity;
        else {
            Log.e(TAG, "setDialogPosition: Gravity type not supported! only Gravity.TOP or Gravity.BOTTOM.");
            return;
        }

        //wlp.flags &= ~WindowManager.LayoutParams.FLAG_DIM_BEHIND;
        wlp.windowAnimations = R.style.bottomDialogAnimation;
        switch (gravity){
            case BOTTOM:
                wlp.windowAnimations = R.style.bottomDialogAnimation;
                break;
            case TOP:
                wlp.windowAnimations = R.style.topDialogAnimation;
                break;
            default:
                wlp.windowAnimations = R.style.customDialogAnimation;
                break;
        }
        dialog.getWindow().setAttributes(wlp);
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)parent.getDialogView().getLayoutParams();
        int topMargin = params.topMargin;
        int leftMargin = params.leftMargin;
        int rightMargin = params.rightMargin;
        int bottomMargin = params.bottomMargin;
        switch (gravity){
            case BOTTOM:
                bottomMargin/=3;
                break;
            case TOP:
                topMargin/=3;
                break;
        }
        params.setMargins(leftMargin,topMargin,rightMargin,bottomMargin);
        parent.getDialogView().setLayoutParams(params);
    }








}
ui/src/main/java/net/crofis/ui/dialog/ActionDialog.java
public class ActionDialog extends BaseAlertDialog{

    private TextView title;
    private TextView description;
    private ListView listView;
    private ArrayList<ActionItem> items;
    private boolean allowAnimation = true;
    private boolean cancelable = true;
    private boolean isLocked = false;
    private boolean showActionIcons = true;

    /**
     * Default ActionDialog constructor.
     * @param context - Application Context.
     * @param items - The action items.
     */

    public ActionDialog(Context context, final ArrayList<ActionItem> items){
        this.context = context;
        this.items = items;
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this.context);
        LayoutInflater inflater = LayoutInflater.from(this.context);
        dialogView = inflater.inflate(R.layout.ui_action_dialog, null);
        title = (TextView) dialogView.findViewById(R.id.dialog_title);
        description = (TextView) dialogView.findViewById(R.id.dialog_description);
        listView = (ListView) dialogView.findViewById(R.id.listView);
        ActionDialogAdapter adapter = new ActionDialogAdapter(context,items);
        listView.setAdapter(adapter);
        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                try {
                    items.get(position).getClickListener().onActionSelected();
                }catch (NullPointerException e){
                    e.printStackTrace();
                }
                dismiss();
            }
        });
        dialogView.findViewById(R.id.background).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });
        dialogBuilder.setView(dialogView);
        dialog = dialogBuilder.create();
        this.dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        if (allowAnimation) dialog.getWindow().getAttributes().windowAnimations = R.style.customDialogAnimation;

    }



    /**
     * Show the dialog.
     */
    @Override
    public void show(){
        dialog.setCancelable(this.cancelable);
        dialog.show();
    }

    /**
     * Dismiss the dialog.
     */
    @Override
    public void dismiss(){
        if(!isLocked) dialog.dismiss();
    }

    /**
     * Sets the text on the dialog's title TextView.
     *
     * Note that if you do not set a title the title TextView's visibility will be set to to gone.
     *
     * @param title is the title you wish to set.
     */
    public void setDialogTitle(String title){
        try {
            setTitleVisable(true);
            this.title.setText(title);
        }catch (NullPointerException e){
            e.printStackTrace();
        }
    }

    /**
     * Sets the text on the dialog's description TextView.
     *
     * Note that if you do not set a description the description TextView's visibility will be set to to gone.
     *
     * @param description is the description you wish to set.
     */
    public void setDialogDescription(String description){
        try{
            setDescriptionVisable(true);
            this.description.setText(description);
        }catch (NullPointerException e){
            e.printStackTrace();
        }
    }

    /**
     * set the title visibility.
     *
     * @param show if true visibility will be VISIBLE, if false it will be set to GONE.
     */
    public void setTitleVisable(boolean show){
        try{
            if(show)this.title.setVisibility(View.VISIBLE);
            else this.title.setVisibility(View.GONE);
        }catch (NullPointerException e){
            e.printStackTrace();
        }
    }

    /**
     * set the description visibility.
     *
     * @param show if true visibility will be VISIBLE, if false it will be set to GONE.
     */
    public void setDescriptionVisable(boolean show){
        try{
            if(show)this.description.setVisibility(View.VISIBLE);
            else this.description.setVisibility(View.GONE);
        }catch (NullPointerException e){
            e.printStackTrace();
        }
    }

    /**
     *
     * Getters and Setters.
     *
     */


    @Override
    public Context getContext() {
        return context;
    }

    public TextView getTitle() {
        return title;
    }

    public ListView getListView() {
        return listView;
    }

    public ArrayList<ActionItem> getItems()    {
        return items;
    }

    @Override
    public View getDialogView() {
        return dialogView;
    }

    public void setDialogView(View dialogView) {
        this.dialogView = dialogView;
    }
    @Override
    public AlertDialog getDialog() {
        return dialog;
    }

    public void setDialog(AlertDialog dialog) {
        this.dialog = dialog;
    }

    public boolean isAllowAnimation() {
        return allowAnimation;
    }

    public void setAllowAnimation(boolean allowAnimation) {
        this.allowAnimation = allowAnimation;
    }

    public boolean isCancelable() {
        return cancelable;
    }

    public void setCancelable(boolean cancelable) {
        this.cancelable = cancelable;
    }

    public boolean isShowActionIcons() {
        return showActionIcons;
    }

    public void setShowActionIcons(boolean showActionIcons) {
        this.showActionIcons = showActionIcons;
    }

    public TextView getDescription() {
        return description;
    }

    /**
     * ListView Adapter Class.
     */
    public class ActionDialogAdapter extends BaseAdapter{

        Context context;
        ArrayList<ActionItem> items;

        public ActionDialogAdapter(Context context,ArrayList<ActionItem> items) {
            this.context = context;
            this.items = items;
        }

        @Override
        public int getCount() {
            return items.size();
        }

        @Override
        public ActionItem getItem(int position) {
            return items.get(position);
        }

        @Override
        public long getItemId(int position) {
            return 0;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            convertView = items.get(position).getView(context,showActionIcons);
            if(convertView == null) {
                convertView = LayoutInflater.from(context).inflate(R.layout.ui_general_action, null, false);
                ((TextView) convertView.findViewById(R.id.action_title)).setText(items.get(position).getTitle());
                if (showActionIcons)
                    ((ImageView) convertView.findViewById(R.id.action_icon)).setImageDrawable(items.get(position).getIcon());
                else convertView.findViewById(R.id.action_icon).setVisibility(View.GONE);
                return convertView;
            }
            return convertView;
        }
    }
}
ui/src/main/java/net/crofis/ui/custom/actionitem/UIAlertAction.java
public class UIAlertAction extends ActionItem {

    @Override
    public String getTitle() {
        return super.getTitle();
    }

    /**
     * Default constructor.
     *
     * @param icon     - Action Icon
     * @param title    - Action Title
     * @param listener - Action Listener
     */



    public UIAlertAction(Drawable icon, String title, ActionItemClickListener listener) {
        super(icon, title, listener);
    }

    @Override
    public View getView(Context context, boolean showActionIcons) {
        View convertView =  LayoutInflater.from(context).inflate(R.layout.ui_general_action, null, false);
        TextView title = (TextView) convertView.findViewById(R.id.action_title);
        title.setText(getTitle());
        title.setGravity(Gravity.CENTER);
        title.setTextColor(context.getResources().getColor(R.color.blue));
        if (false)((ImageView) convertView.findViewById(R.id.action_icon)).setImageDrawable(getIcon());
        else convertView.findViewById(R.id.action_icon).setVisibility(View.GONE);
        return convertView;
    }
}
ui/src/main/java/net/crofis/ui/dialog/LoadingDialog.java
public class LoadingDialog extends BaseAlertDialog{

    private static final String TAG = "LoadingDialog.class";

    /**Title of the dialog**/
    private TextView title;

    /**Message of the dialog**/
    private TextView message;

    /**Progress Bar**/
    private SmoothProgressBar progressView;

    /**Positive Button**/
    private FloatingActionButton postiveButton;

    /**In charge of the slide in animation**/
    private boolean allowAnimation = true;

    /**Is dialog cancelable**/
    private boolean cancelable = false;

    /**Is the dialog locked**/
    private boolean isLocked = false;

    /**The delay that takes the button to appear after .complete() is called**/
    private long show_delay = 300;

    /**The delay that is added to dismiss the dialog after .complete() is called, NOTE: this is only used when autoDismiss is true**/
    private long extra_delay = 200;

    /**Just some default text to display TODO: Replace with string from resources**/
    private final String text_completed = "Progress Finished";

    /**Used to execute custom code when .complete() is called. null by default**/
    private PostCompleted postComplete = null;

    /**
     * Default constructor
     * @param context - Application context.
     */
    public LoadingDialog(Context context){
        this.context = context;
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this.context);
        LayoutInflater inflater = LayoutInflater.from(this.context);
        dialogView = inflater.inflate(R.layout.ui_loading_dialog, null);
        this.title = (TextView) dialogView.findViewById(R.id.dialog_title);
        this.title.setText("");
        this.message= (TextView) dialogView.findViewById(R.id.dialog_message);
        this.message.setText("");
        this.message.setMovementMethod(new ScrollingMovementMethod());
        this.progressView = (SmoothProgressBar) dialogView.findViewById(R.id.progress_bar);
        this.postiveButton = (FloatingActionButton) dialogView.findViewById(R.id.pos);
        this.postiveButton.hide(false);
        this.postiveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dismiss();
            }
        });
        dialogBuilder.setView(dialogView);
        dialog = dialogBuilder.create();
        this.dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        if (allowAnimation)
            dialog.getWindow().getAttributes().windowAnimations = R.style.customDialogAnimation;
    }

    /**
     * Init with parameters.
     * @param context - Application context.
     * @param title - title of the dialog.
     */
    public LoadingDialog(Context context,String title){
        this.context = context;
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this.context);
        LayoutInflater inflater = LayoutInflater.from(this.context);
        dialogView = inflater.inflate(R.layout.ui_loading_dialog, null);
        this.title = (TextView) dialogView.findViewById(R.id.dialog_title);
        this.title.setText(title == null || title.length()==0 ? "" : title);//validate string
        this.message= (TextView) dialogView.findViewById(R.id.dialog_message);
        this.message.setText("");
        this.message.setMovementMethod(new ScrollingMovementMethod());
        this.progressView = (SmoothProgressBar) dialogView.findViewById(R.id.progress_bar);

        this.postiveButton = (FloatingActionButton) dialogView.findViewById(R.id.pos);
        this.postiveButton.hide(false);
        this.postiveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dismiss();
            }
        });
        dialogBuilder.setView(dialogView);
        dialog = dialogBuilder.create();
        this.dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        if (allowAnimation)
            dialog.getWindow().getAttributes().windowAnimations = R.style.customDialogAnimation;
    }

    /**
     * Init with parameters.
     * @param context - Application context.
     * @param title - title of the dialog.
     * @param message - title of the message.
     */
    public LoadingDialog(Context context,String title,String message){
        this.context = context;
        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this.context);
        LayoutInflater inflater = LayoutInflater.from(this.context);
        dialogView = inflater.inflate(R.layout.ui_loading_dialog, null);
        this.title = (TextView) dialogView.findViewById(R.id.dialog_title);
        this.title.setText(title == null || title.length()==0 ? "" : title);//validate string
        this.message= (TextView) dialogView.findViewById(R.id.dialog_message);
        this.message.setText(message == null || title.length() == 0 ? "" : message);//validate string
        this.message.setMovementMethod(new ScrollingMovementMethod());
        this.progressView = (SmoothProgressBar) dialogView.findViewById(R.id.progress_bar);

        this.postiveButton = (FloatingActionButton) dialogView.findViewById(R.id.pos);
        this.postiveButton.hide(false);
        this.postiveButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                dismiss();
            }
        });
        dialogView.findViewById(R.id.background).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });
        dialogBuilder.setView(dialogView);
        dialog = dialogBuilder.create();
        this.dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
        if (allowAnimation)
            dialog.getWindow().getAttributes().windowAnimations = R.style.customDialogAnimation;
    }

    /**
     * Show the dialog.
     */
    @Override
    public void show(){

        dialog.setCancelable(this.cancelable);
        dialog.show();
    }

    /**
     * Dismiss the dialog.
     */
    @Override
    public void dismiss(){
        if(!isLocked) dialog.dismiss();
        else Log.e(TAG, "Dialog is locked.");
    }

    /**
     * .complete() method is used to stop the progress bar (call "finish loading" animation)
     * @param autoDismiss - whether the dialog should dismiss after calling .complete().
     */
    public void complete(final boolean autoDismiss){
        if(!autoDismiss) title.setText("Completed");
        progressView.progressiveStop();
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                if(autoDismiss) dismiss();
                else {
                    postiveButton.show(allowAnimation);
                    message.setText(text_completed);
                }
            }
        }, show_delay + (autoDismiss ? extra_delay : 0));

        this.dialog.setCancelable(true);
    }

    /**
     * .complete() method is used to stop the progress bar (call "finish loading" animation)
     *
     * @param autoDismiss - whether the dialog should dismiss after calling .complete().
     * @param isPositive - if the button that will be dismissed will look postive (Green) or negative (red).
     * @param newTitle - the new title that will be shown after .complete() is called, will be ignored if autoDismiss is true.
     * @param newMsg - the new message that will be shown after .complete() is called, will be ignored if autoDismiss is true.
     */
    public void complete(final boolean autoDismiss,final boolean isPositive,final String newTitle,final String newMsg){
        if(!autoDismiss) if(newTitle!=null)title.setText(newTitle);
        progressView.progressiveStop();
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                if (autoDismiss) dismiss();
                else {
                    if (isPositive) postiveButton.show(allowAnimation);
                    else {
                        //display negative button
                        //TODO: change the look of the postive button
                        postiveButton.setColorNormalResId(R.color.red);
                        postiveButton.setColorPressedResId(R.color.red_dark);
                        postiveButton.setColorRippleResId(R.color.red_light);
                        postiveButton.setImageResource(R.drawable.ic_clear_white_24dp);
                        postiveButton.show(allowAnimation);
                    }
                    if(newMsg!=null) message.setText(newMsg);
                    onCompleted(postComplete);
                }
            }
        }, show_delay + (autoDismiss ? extra_delay : 0));
        this.dialog.setCancelable(true);
    }

    /**
     * This automatically executes the code that inside postComplete runnable.
     * is called when .complete() is called. will be ignored if postComplete is null.
     *
     * @param toExecute - is the runnable that will be executed.
     */
    private void onCompleted(PostCompleted toExecute){
        if(toExecute==null) return;
        toExecute.onComplete(this);
    }

    public void setProgressBarColor(String color){
        try {
            this.progressView.setSmoothProgressDrawableColor(Color.parseColor(color));
        } catch (Exception g){
            g.printStackTrace();
        }
    }


    /**
     *
     * Getters and Setters.
     *
     */

    public boolean isAllowAnimation() {
        return allowAnimation;
    }

    public void setAllowAnimation(boolean allowAnimation) {
        this.allowAnimation = allowAnimation;
    }

    public boolean isCancelable() {
        return cancelable;
    }

    public void setCancelable(boolean cancelable) {
        this.cancelable = cancelable;
    }
    @Override
    public Context getContext() {
        return context;
    }

    public void setContext(Context context) {
        this.context = context;
    }
    @Override
    public AlertDialog getDialog() {
        return dialog;
    }
    @Override
    public View getDialogView() {
        return dialogView;
    }


    public long getExtra_delay() {
        return extra_delay;
    }

    public void setExtra_delay(long extra_delay) {
        this.extra_delay = extra_delay;
    }

    public boolean isLocked() {
        return isLocked;
    }

    public void setIsLocked(boolean isLocked) {
        this.isLocked = isLocked;
    }

    public TextView getMessage() {
        return message;
    }

    @Deprecated
    public void setPostComplete(Runnable postComplete) {
        //this.postComplete = postComplete;
    }

    public void setPostComplete(PostCompleted todo){
        this.postComplete = todo;
    }

    public FloatingActionButton getPostiveButton() {
        return postiveButton;
    }

    public SmoothProgressBar getProgressView() {
        return progressView;
    }

    public long getShow_delay() {
        return show_delay;
    }

    public void setShow_delay(long show_delay) {
        this.show_delay = show_delay;
    }

    public TextView getTitle() {
        return title;
    }

    public interface PostCompleted{
        void onComplete(LoadingDialog dialog);
    }

}
ui/src/main/java/net/crofis/ui/custom/PreviewSurfaceView.java
public class PreviewSurfaceView extends SurfaceView {

    /**The parent class that is holding the surface view**/
    private Object camPreview;

    /**Tracker that checks if a lister was set or not**/
    private boolean listenerSet = false;

    /**Tracker that checks if a frame was set or not**/
    private boolean frame_set;

    /**The frame that holds the surface view**/
    private View frame;

    /**Tracker that checks if the drawing was removed, is used to prevent multiple drawings at a time**/
    private boolean isRemoved=true;

    /**The drawing object**/
    private DrawingView dv;

    /**Flag that checks if interaction is enabled**/
    public boolean allow_touch=true;

    /**The color of the drawing view**/
    private String focus_border_color;

    /**The stroke size of the drawing view**/
    private int focus_border_stroke = 0;

    /**The type of the camPreview (ranges from 0~2)**/
    private int type = 0;

    public int squareSize = 100;

    /**
     * Default constructors
     */
    public PreviewSurfaceView(Context context) {
        super(context);
    }

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

    public PreviewSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public PreviewSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }


    /**
     * onTouchEvent, overrided to handle foucs touchs.
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(!allow_touch) return false; //check if interaction is enabled.

        if(!listenerSet) return false; //check if surface has parent.

        //case action type is ACTION_DOWN
        if(event.getAction() == MotionEvent.ACTION_DOWN){

            //get touch location
            float x = event.getX();
            float y = event.getY();
            squareSize = this.getWidth()/12;
            //create rect
            Rect touchRect = new Rect(
                    (int)(x - squareSize),
                    (int)(y - squareSize),
                    (int)(x + squareSize),
                    (int)(y + squareSize));


            final Rect targetFocusRect = new Rect(
                    touchRect.left * 2000/this.getWidth() - 1000,
                    touchRect.top * 2000/this.getHeight() - 1000,
                    touchRect.right * 2000/this.getWidth() - 1000,
                    touchRect.bottom * 2000/this.getHeight() - 1000);

            //check which kind of method to call according to the type of the parent class.
            switch(type){
                /**
                 * init the focus callback from the parent class.
                 */
                case 0:
                    ((CameraActivity) camPreview).doTouchFocus(targetFocusRect);
                    break;
                case 1:
                    ((SquareCameraActivity)camPreview).doTouchFocus(targetFocusRect);
                    break;
                case 2:
                    ((CameraDialog) camPreview).doTouchFocus(targetFocusRect);
                    break;
            }

            //check if a frame was set and if the drawing view was already removed.
            if(frame_set&&isRemoved){
                isRemoved=false; //set isRemoved to false to avoid multiple focus views.
                dv = new DrawingView(getContext()); //initialize the drawing view.
                //noinspection ResourceType
                dv.setShapeColor(getResources().getString(android.R.color.white)); //set the shape color.
                if(focus_border_color!=null) dv.setShapeColor(focus_border_color);
                if(focus_border_stroke>0) dv.setStrokeSize(focus_border_stroke);//set the shape stroke size.

                dv.setHaveTouch(true, touchRect); //set the position of the drawing view object.
                ((FrameLayout) frame).addView(dv); //add the drawing view object to the FrameLayout that is wrapping the SurfaceView.

                //Special case fix for Camera Dialog
                if(type==2) {
                    //Create a FrameLayout Parameter object with the height of the current SurfaceView.
                    FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) this.getLayoutParams();
                    params.height = this.getHeight();
                    //Set the params on the FrameLayout, this will force it to stay in the same size and not expand.
                    frame.setLayoutParams(params);
                }

                //Invalidate the view.
                dv.invalidate();

                //Create handler object to remove the object.
                Handler handler = new Handler();

                //set post delay 1.5 seconds.
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        //Re-position the view.
                        dv.setHaveTouch(false, new Rect(0, 0, 0, 0));
                        dv.invalidate();

                        //remove the view from the frame layout.
                        ((FrameLayout) frame).removeView(dv);

                        //change the isRemoved flag to true.
                        isRemoved = true;
                    }
                }, 1500);

            }
        }

        return false;
    }


    public void setListener(CameraActivity camPreview) {
        this.camPreview = camPreview;
        listenerSet = true;
        type = 0;
    }

    public void setListener(SquareCameraActivity camPreview){
        this.camPreview = camPreview;
        listenerSet = true;
        type = 1;
    }

    public void setListener(CameraDialog camPreview){
        this.camPreview = camPreview;
        listenerSet = true;
        type = 2;
    }

    public void setFrame(View frame){
        this.frame = frame;
        frame_set= true;
    }



}
ui/src/main/java/net/crofis/ui/custom/cropper/CropImageView.java
public class CropImageView extends FrameLayout {

    //region: Fields and Consts

    /**
     * Image view widget used to show the image for cropping.
     */
    private final ImageView mImageView;

    /**
     * Overlay over the image view to show cropping UI.
     */
    private final CropOverlayView mCropOverlayView;

    /**
     * The matrix used to transform the cripping image in the image view
     */
    private final Matrix mImageMatrix = new Matrix();

    /**
     * Reusing matrix instance for reverse matrix calculations.
     */
    private final Matrix mImageInverseMatrix = new Matrix();

    /**
     * Progress bar widget to show progress bar on async image loading and cropping.
     */
    private final ProgressBar mProgressBar;

    /**
     * Rectengale used in image matrix transformation calculation (reusing rect instance)
     */
    private final RectF mImageRect = new RectF();

    /**
     * Animation class to smooth animate zoom-in/out
     */
    private CropImageAnimation mAnimation;

    private Bitmap mBitmap;

    private int mDegreesRotated;

    private int mLayoutWidth;

    private int mLayoutHeight;

    private int mImageResource;

    /**
     * The initial scale type of the image in the crop image view
     */
    private ScaleType mScaleType;

    /**
     * if to show crop overlay UI what contains the crop window UI surrounded by background over the cropping
     * image.<br>
     * default: true, may disable for animation or frame transition.
     */
    private boolean mShowCropOverlay = true;

    /**
     * if to show progress bar when image async loading/cropping is in progress.<br>
     * default: true, disable to provide custom progress bar UI.
     */
    private boolean mShowProgressBar = true;

    /**
     * if auto-zoom functionality is enabled.<br>
     * default: true.
     */
    private boolean mAutoZoomEnabled = true;

    /**
     * The max zoom allowed during cropping
     */
    private int mMaxZoom;

    /**
     * callback to be invoked when image async loading is complete
     */
    private WeakReference<OnSetImageUriCompleteListener> mOnSetImageUriCompleteListener;

    /**
     * callback to be invoked when image async cropping is complete (get bitmap)
     */
    private WeakReference<OnGetCroppedImageCompleteListener> mOnGetCroppedImageCompleteListener;

    /**
     * callback to be invoked when image async cropping is complete (save to uri)
     */
    private WeakReference<OnSaveCroppedImageCompleteListener> mOnSaveCroppedImageCompleteListener;

    /**
     * The URI that the image was loaded from (if loaded from URI)
     */
    private Uri mLoadedImageUri;

    /**
     * The sample size the image was loaded by if was loaded by URI
     */
    private int mLoadedSampleSize = 1;

    /**
     * The current zoom level to to scale the cropping image
     */
    private float mZoom = 1;

    /**
     * The X offset that the cropping image was translated after zooming
     */
    private float mZoomOffsetX;

    /**
     * The Y offset that the cropping image was translated after zooming
     */
    private float mZoomOffsetY;

    /**
     * Used to restore the cropping windows rectangle after state restore
     */
    private RectF mRestoreCropWindowRect;

    /**
     * Task used to load bitmap async from UI thread
     */
    private WeakReference<BitmapLoadingWorkerTask> mBitmapLoadingWorkerTask;

    /**
     * Task used to crop bitmap async from UI thread
     */
    private WeakReference<BitmapCroppingWorkerTask> mBitmapCroppingWorkerTask;
    //endregion

    public CropImageView(Context context) {
        this(context, null);
    }

    public CropImageView(Context context, AttributeSet attrs) {
        super(context, attrs);

        CropImageOptions options = null;
        Intent intent = context instanceof Activity ? ((Activity) context).getIntent() : null;
        if (intent != null) {
            options = intent.getParcelableExtra(CropImage.CROP_IMAGE_EXTRA_OPTIONS);
        }

        if (options == null) {

            options = new CropImageOptions();

            if (attrs != null) {
                TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CropImageView, 0, 0);
                try {
                    options.fixAspectRatio = ta.getBoolean(R.styleable.CropImageView_cropFixAspectRatio, options.fixAspectRatio);
                    options.aspectRatioX = ta.getInteger(R.styleable.CropImageView_cropAspectRatioX, options.aspectRatioX);
                    options.aspectRatioY = ta.getInteger(R.styleable.CropImageView_cropAspectRatioY, options.aspectRatioY);
                    options.scaleType = ScaleType.values()[ta.getInt(R.styleable.CropImageView_cropScaleType, options.scaleType.ordinal())];
                    options.autoZoomEnabled = ta.getBoolean(R.styleable.CropImageView_cropAutoZoomEnabled, options.autoZoomEnabled);
                    options.maxZoom = ta.getInteger(R.styleable.CropImageView_cropMaxZoom, options.maxZoom);
                    options.cropShape = CropShape.values()[ta.getInt(R.styleable.CropImageView_cropShape, options.cropShape.ordinal())];
                    options.guidelines = Guidelines.values()[ta.getInt(R.styleable.CropImageView_cropGuidelines, options.guidelines.ordinal())];
                    options.snapRadius = ta.getDimension(R.styleable.CropImageView_cropSnapRadius, options.snapRadius);
                    options.touchRadius = ta.getDimension(R.styleable.CropImageView_cropTouchRadius, options.touchRadius);
                    options.initialCropWindowPaddingRatio = ta.getFloat(R.styleable.CropImageView_cropInitialCropWindowPaddingRatio, options.initialCropWindowPaddingRatio);
                    options.borderLineThickness = ta.getDimension(R.styleable.CropImageView_cropBorderLineThickness, options.borderLineThickness);
                    options.borderLineColor = ta.getInteger(R.styleable.CropImageView_cropBorderLineColor, options.borderLineColor);
                    options.borderCornerThickness = ta.getDimension(R.styleable.CropImageView_cropBorderCornerThickness, options.borderCornerThickness);
                    options.borderCornerOffset = ta.getDimension(R.styleable.CropImageView_cropBorderCornerOffset, options.borderCornerOffset);
                    options.borderCornerLength = ta.getDimension(R.styleable.CropImageView_cropBorderCornerLength, options.borderCornerLength);
                    options.borderCornerColor = ta.getInteger(R.styleable.CropImageView_cropBorderCornerColor, options.borderCornerColor);
                    options.guidelinesThickness = ta.getDimension(R.styleable.CropImageView_cropGuidelinesThickness, options.guidelinesThickness);
                    options.guidelinesColor = ta.getInteger(R.styleable.CropImageView_cropGuidelinesColor, options.guidelinesColor);
                    options.backgroundColor = ta.getInteger(R.styleable.CropImageView_cropBackgroundColor, options.backgroundColor);
                    options.showCropOverlay = ta.getBoolean(R.styleable.CropImageView_cropShowCropOverlay, mShowCropOverlay);
                    options.showProgressBar = ta.getBoolean(R.styleable.CropImageView_cropShowProgressBar, mShowProgressBar);
                    options.borderCornerThickness = ta.getDimension(R.styleable.CropImageView_cropBorderCornerThickness, options.borderCornerThickness);
                    options.minCropWindowWidth = (int) ta.getDimension(R.styleable.CropImageView_cropMinCropWindowWidth, options.minCropWindowWidth);
                    options.minCropWindowHeight = (int) ta.getDimension(R.styleable.CropImageView_cropMinCropWindowHeight, options.minCropWindowHeight);
                    options.minCropResultWidth = (int) ta.getFloat(R.styleable.CropImageView_cropMinCropResultWidthPX, options.minCropResultWidth);
                    options.minCropResultHeight = (int) ta.getFloat(R.styleable.CropImageView_cropMinCropResultHeightPX, options.minCropResultHeight);
                    options.maxCropResultWidth = (int) ta.getFloat(R.styleable.CropImageView_cropMaxCropResultWidthPX, options.maxCropResultWidth);
                    options.maxCropResultHeight = (int) ta.getFloat(R.styleable.CropImageView_cropMaxCropResultHeightPX, options.maxCropResultHeight);
                } finally {
                    ta.recycle();
                }
            }
        }

        options.validate();

        mScaleType = options.scaleType;
        mAutoZoomEnabled = options.autoZoomEnabled;
        mMaxZoom = options.maxZoom;
        mShowCropOverlay = options.showCropOverlay;
        mShowProgressBar = options.showProgressBar;

        LayoutInflater inflater = LayoutInflater.from(context);
        View v = inflater.inflate(R.layout.crop_image_view, this, true);

        mImageView = (ImageView) v.findViewById(R.id.ImageView_image);
        mImageView.setScaleType(ImageView.ScaleType.MATRIX);

        mCropOverlayView = (CropOverlayView) v.findViewById(R.id.CropOverlayView);
        mCropOverlayView.setCropWindowChangeListener(new CropOverlayView.CropWindowChangeListener() {
            @Override
            public void onCropWindowChanged(boolean inProgress) {
                handleCropWindowChanged(inProgress, true);
            }
        });
        mCropOverlayView.setInitialAttributeValues(options);

        mProgressBar = (ProgressBar) v.findViewById(R.id.CropProgressBar);
        setProgressBarVisibility();
    }

    /**
     * Get the scale type of the image in the crop view.
     */
    public ScaleType getScaleType() {
        return mScaleType;
    }

    /**
     * Set the scale type of the image in the crop view
     */
    public void setScaleType(ScaleType scaleType) {
        if (scaleType != mScaleType) {
            mScaleType = scaleType;
            mZoom = 1;
            mZoomOffsetX = mZoomOffsetY = 0;
            mCropOverlayView.resetCropOverlayView();
            requestLayout();
        }
    }

    /**
     * The shape of the cropping area - rectangle/circular.
     */
    public CropShape getCropShape() {
        return mCropOverlayView.getCropShape();
    }

    /**
     * The shape of the cropping area - rectangle/circular.
     */
    public void setCropShape(CropShape cropShape) {
        mCropOverlayView.setCropShape(cropShape);
    }

    /**
     * if auto-zoom functionality is enabled. default: true.
     */
    public boolean isAutoZoomEnabled() {
        return mAutoZoomEnabled;
    }

    /**
     * Set auto-zoom functionality to enabled/disabled.
     */
    public void setAutoZoomEnabled(boolean autoZoomEnabled) {
        if (mAutoZoomEnabled != autoZoomEnabled) {
            mAutoZoomEnabled = autoZoomEnabled;
            handleCropWindowChanged(false, false);
            mCropOverlayView.invalidate();
        }
    }

    /**
     * The max zoom allowed during cropping.
     */
    public int getMaxZoom() {
        return mMaxZoom;
    }

    /**
     * The max zoom allowed during cropping.
     */
    public void setMaxZoom(int maxZoom) {
        if (mMaxZoom != maxZoom && maxZoom > 0) {
            mMaxZoom = maxZoom;
            handleCropWindowChanged(false, false);
            mCropOverlayView.invalidate();
        }
    }

    /**
     * Get the amount of degrees the cropping image is rotated cloackwise.<br>
     *
     * @return 0-360
     */
    public int getRotatedDegrees() {
        return mDegreesRotated;
    }

    /**
     * Set the amount of degrees the cropping image is rotated cloackwise.<br>
     *
     * @param degrees 0-360
     */
    public void setRotatedDegrees(int degrees) {
        if (mDegreesRotated != degrees) {
            rotateImage(degrees - mDegreesRotated);
        }
    }

    /**
     * whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows it to be changed.
     */
    public boolean isFixAspectRatio() {
        return mCropOverlayView.isFixAspectRatio();
    }

    /**
     * Sets whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows it to be changed.
     */
    public void setFixedAspectRatio(boolean fixAspectRatio) {
        mCropOverlayView.setFixedAspectRatio(fixAspectRatio);
    }

    /**
     * Get the current guidelines option set.
     */
    public Guidelines getGuidelines() {
        return mCropOverlayView.getGuidelines();
    }

    /**
     * Sets the guidelines for the CropOverlayView to be either on, off, or to show when resizing the application.
     */
    public void setGuidelines(Guidelines guidelines) {
        mCropOverlayView.setGuidelines(guidelines);
    }

    /**
     * both the X and Y values of the aspectRatio.
     */
    public Pair<Integer, Integer> getAspectRatio() {
        return new Pair<>(mCropOverlayView.getAspectRatioX(), mCropOverlayView.getAspectRatioY());
    }

    /**
     * Sets the both the X and Y values of the aspectRatio.
     *
     * @param aspectRatioX int that specifies the new X value of the aspect ratio
     * @param aspectRatioY int that specifies the new Y value of the aspect ratio
     */
    public void setAspectRatio(int aspectRatioX, int aspectRatioY) {
        mCropOverlayView.setAspectRatioX(aspectRatioX);
        mCropOverlayView.setAspectRatioY(aspectRatioY);
    }

    /**
     * An edge of the crop window will snap to the corresponding edge of a
     * specified bounding box when the crop window edge is less than or equal to
     * this distance (in pixels) away from the bounding box edge. (default: 3dp)
     */
    public void setSnapRadius(float snapRadius) {
        if (snapRadius >= 0) {
            mCropOverlayView.setSnapRadius(snapRadius);
        }
    }

    /**
     * if to show progress bar when image async loading/cropping is in progress.<br>
     * default: true, disable to provide custom progress bar UI.
     */
    public boolean isShowProgressBar() {
        return mShowProgressBar;
    }

    /**
     * if to show progress bar when image async loading/cropping is in progress.<br>
     * default: true, disable to provide custom progress bar UI.
     */
    public void setShowProgressBar(boolean showProgressBar) {
        if (mShowProgressBar != showProgressBar) {
            mShowProgressBar = showProgressBar;
            setProgressBarVisibility();
        }
    }

    /**
     * if to show crop overlay UI what contains the crop window UI surrounded by background over the cropping
     * image.<br>
     * default: true, may disable for animation or frame transition.
     */
    public boolean isShowCropOverlay() {
        return mShowCropOverlay;
    }

    /**
     * if to show crop overlay UI what contains the crop window UI surrounded by background over the cropping
     * image.<br>
     * default: true, may disable for animation or frame transition.
     */
    public void setShowCropOverlay(boolean showCropOverlay) {
        if (mShowCropOverlay != showCropOverlay) {
            mShowCropOverlay = showCropOverlay;
            setCropOverlayVisibility();
        }
    }

    /**
     * Returns the integer of the imageResource
     */
    public int getImageResource() {
        return mImageResource;
    }

    /**
     * Get the URI of an image that was set by URI, null otherwise.
     */
    public Uri getImageUri() {
        return mLoadedImageUri;
    }

    /**
     * Gets the crop window's position relative to the source Bitmap (not the image
     * displayed in the CropImageView) using the original image rotation.
     *
     * @return a Rect instance containing cropped area boundaries of the source Bitmap
     */
    public Rect getCropRect() {
        if (mBitmap != null) {

            // get the points of the crop rectangle adjusted to source bitmap
            float[] points = getCropPoints();

            int orgWidth = mBitmap.getWidth() * mLoadedSampleSize;
            int orgHeight = mBitmap.getHeight() * mLoadedSampleSize;

            // get the rectangle for the points (it may be larger than original if rotation is not stright)
            return BitmapUtils.getRectFromPoints(points, orgWidth, orgHeight,
                    mCropOverlayView.isFixAspectRatio(), mCropOverlayView.getAspectRatioX(), mCropOverlayView.getAspectRatioY());
        } else {
            return null;
        }
    }

    /**
     * Gets the 4 points of crop window's position relative to the source Bitmap (not the image
     * displayed in the CropImageView) using the original image rotation.<br>
     * Note: the 4 points may not be a rectangle if the image was rotates to NOT stright angle (!= 90/180/270).
     *
     * @return 4 points (x0,y0,x1,y1,x2,y2,x3,y3) of cropped area boundaries
     */
    public float[] getCropPoints() {

        // Get crop window position relative to the displayed image.
        RectF cropWindowRect = mCropOverlayView.getCropWindowRect();

        float[] points = new float[]{
                cropWindowRect.left,
                cropWindowRect.top,
                cropWindowRect.right,
                cropWindowRect.top,
                cropWindowRect.right,
                cropWindowRect.bottom,
                cropWindowRect.left,
                cropWindowRect.bottom
        };

        mImageMatrix.invert(mImageInverseMatrix);
        mImageInverseMatrix.mapPoints(points);

        for (int i = 0; i < points.length; i++) {
            points[i] *= mLoadedSampleSize;
        }

        return points;
    }

    /**
     * Set the crop window position and size to the given rectangle.<br>
     * Image to crop must be first set before invoking this, for async - after complete callback.
     *
     * @param rect window rectangle (position and size) relative to source bitmap
     */
    public void setCropRect(Rect rect) {
        mCropOverlayView.setInitialCropWindowRect(rect);
    }


    /**
     * Reset crop window to initial rectangle.
     */
    public void resetCropRect() {
        mZoom = 1;
        mZoomOffsetX = 0;
        mZoomOffsetY = 0;
        mDegreesRotated = 0;
        applyImageMatrix(getWidth(), getHeight(), false, false);
        mCropOverlayView.resetCropWindowRect();
    }

    /**
     * Gets the cropped image based on the current crop window.
     *
     * @return a new Bitmap representing the cropped image
     */
    public Bitmap getCroppedImage() {
        return getCroppedImage(0, 0);
    }

    /**
     * Gets the cropped image based on the current crop window.<br>
     * If image loaded from URI will use sample size to fit in the requested width and height down-sampling
     * if required - optimization to get best size to quality.<br>
     * NOTE: resulting image will not be exactly (reqWidth, reqHeight)
     * see: <a href="http://developer.android.com/training/displaying-bitmaps/load-bitmap.html">Loading Large
     * Bitmaps Efficiently</a>
     *
     * @param reqWidth the width to downsample the cropped image to
     * @param reqHeight the height to downsample the cropped image to
     * @return a new Bitmap representing the cropped image
     */
    public Bitmap getCroppedImage(int reqWidth, int reqHeight) {
        Bitmap croppedBitmap = null;
        if (mBitmap != null) {
            mImageView.clearAnimation();
            if (mLoadedImageUri != null && mLoadedSampleSize > 1) {
                int orgWidth = mBitmap.getWidth() * mLoadedSampleSize;
                int orgHeight = mBitmap.getHeight() * mLoadedSampleSize;
                croppedBitmap = BitmapUtils.cropBitmap(getContext(), mLoadedImageUri, getCropPoints(),
                        mDegreesRotated, orgWidth, orgHeight,
                        mCropOverlayView.isFixAspectRatio(), mCropOverlayView.getAspectRatioX(), mCropOverlayView.getAspectRatioY(),
                        reqWidth, reqHeight);
            } else {
                croppedBitmap = BitmapUtils.cropBitmap(mBitmap, getCropPoints(), mDegreesRotated,
                        mCropOverlayView.isFixAspectRatio(), mCropOverlayView.getAspectRatioX(), mCropOverlayView.getAspectRatioY());
            }
        }

        return croppedBitmap;
    }

    /**
     * Gets the cropped image based on the current crop window.<br>
     * The result will be invoked to listener set by {@link #setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener)}.
     */
    public void getCroppedImageAsync() {
        getCroppedImageAsync(0, 0);
    }

    /**
     * Gets the cropped image based on the current crop window.<br>
     * If (reqWidth,reqHeight) is given AND image is loaded from URI cropping will try to use sample size to fit in
     * the requested width and height down-sampling if possible - optimization to get best size to quality.<br>
     * NOTE: resulting image will not be exactly (reqWidth, reqHeight)
     * see: <a href="http://developer.android.com/training/displaying-bitmaps/load-bitmap.html">Loading Large
     * Bitmaps Efficiently</a><br>
     * The result will be invoked to listener set by {@link #setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener)}.
     *
     * @param reqWidth the width to downsample the cropped image to
     * @param reqHeight the height to downsample the cropped image to
     */
    public void getCroppedImageAsync(int reqWidth, int reqHeight) {
        if (mOnGetCroppedImageCompleteListener == null) {
            throw new IllegalArgumentException("OnGetCroppedImageCompleteListener is not set");
        }
        startCropWorkerTask(reqWidth, reqHeight, null, null, 0);
    }

    /**
     * Save the cropped image based on the current crop window to the given uri.<br>
     * Uses JPEG image compression with 90 compression quality.<br>
     * The result will be invoked to listener set by {@link #setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener)}.
     *
     * @param saveUri the Android Uri to save the cropped image to
     */
    public void saveCroppedImageAsync(Uri saveUri) {
        saveCroppedImageAsync(saveUri, Bitmap.CompressFormat.JPEG, 90, 0, 0);
    }

    /**
     * Save the cropped image based on the current crop window to the given uri.<br>
     * The result will be invoked to listener set by {@link #setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener)}.
     *
     * @param saveUri the Android Uri to save the cropped image to
     * @param saveCompressFormat the compression format to use when writting the image
     * @param saveCompressQuality the quility (if applicable) to use when writting the image (0 - 100)
     */
    public void saveCroppedImageAsync(Uri saveUri, Bitmap.CompressFormat saveCompressFormat, int saveCompressQuality) {
        saveCroppedImageAsync(saveUri, saveCompressFormat, saveCompressQuality, 0, 0);
    }

    /**
     * Save the cropped image based on the current crop window to the given uri.<br>
     * If (reqWidth,reqHeight) is given AND image is loaded from URI cropping will try to use sample size to fit in
     * the requested width and height down-sampling if possible - optimization to get best size to quality.<br>
     * NOTE: resulting image will not be exactly (reqWidth, reqHeight)
     * see: <a href="http://developer.android.com/training/displaying-bitmaps/load-bitmap.html">Loading Large
     * Bitmaps Efficiently</a><br>
     * The result will be invoked to listener set by {@link #setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener)}.
     *
     * @param saveUri the Android Uri to save the cropped image to
     * @param saveCompressFormat the compression format to use when writting the image
     * @param saveCompressQuality the quility (if applicable) to use when writting the image (0 - 100)
     * @param reqWidth the width to downsample the cropped image to
     * @param reqHeight the height to downsample the cropped image to
     */
    public void saveCroppedImageAsync(Uri saveUri, Bitmap.CompressFormat saveCompressFormat, int saveCompressQuality, int reqWidth, int reqHeight) {
        if (mOnSaveCroppedImageCompleteListener == null) {
            throw new IllegalArgumentException("mOnSaveCroppedImageCompleteListener is not set");
        }
        startCropWorkerTask(reqWidth, reqHeight, saveUri, saveCompressFormat, saveCompressQuality);
    }

    /**
     * Set the callback to be invoked when image async loading ({@link #setImageUriAsync(Uri)})
     * is complete (successful or failed).
     */
    public void setOnSetImageUriCompleteListener(OnSetImageUriCompleteListener listener) {
        mOnSetImageUriCompleteListener = listener != null ? new WeakReference<>(listener) : null;
    }

    /**
     * Set the callback to be invoked when image async get cropping image ({@link #getCroppedImageAsync()})
     * is complete (successful or failed).
     */
    public void setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener listener) {
        mOnGetCroppedImageCompleteListener = listener != null ? new WeakReference<>(listener) : null;
    }

    /**
     * Set the callback to be invoked when image async save cropping image ({@link #saveCroppedImageAsync(Uri)})
     * is complete (successful or failed).
     */
    public void setOnSaveCroppedImageCompleteListener(OnSaveCroppedImageCompleteListener listener) {
        mOnSaveCroppedImageCompleteListener = listener != null ? new WeakReference<>(listener) : null;
    }

    /**
     * Sets a Bitmap as the content of the CropImageView.
     *
     * @param bitmap the Bitmap to set
     */
    public void setImageBitmap(Bitmap bitmap) {
        mCropOverlayView.setInitialCropWindowRect(null);
        setBitmap(bitmap, true);
    }

    /**
     * Sets a Bitmap and initializes the image rotation according to the EXIT data.<br>
     * <br>
     * The EXIF can be retrieved by doing the following:
     * <code>ExifInterface exif = new ExifInterface(path);</code>
     *
     * @param bitmap the original bitmap to set; if null, this
     * @param exif the EXIF information about this bitmap; may be null
     */
    public void setImageBitmap(Bitmap bitmap, ExifInterface exif) {
        Bitmap setBitmap;
        if (bitmap != null && exif != null) {
            BitmapUtils.RotateBitmapResult result = BitmapUtils.rotateBitmapByExif(bitmap, exif);
            setBitmap = result.bitmap;
            mDegreesRotated = result.degrees;
        } else {
            setBitmap = bitmap;
        }
        mCropOverlayView.setInitialCropWindowRect(null);
        setBitmap(setBitmap, true);
    }

    /**
     * Sets a Drawable as the content of the CropImageView.
     *
     * @param resId the drawable resource ID to set
     */
    public void setImageResource(int resId) {
        if (resId != 0) {
            mCropOverlayView.setInitialCropWindowRect(null);
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);
            setBitmap(bitmap, true);
            mImageResource = resId;
        }
    }

    /**
     * Sets a bitmap loaded from the given Android URI as the content of the CropImageView.<br>
     * Can be used with URI from gallery or camera source.<br>
     * Will rotate the image by exif data.<br>
     *
     * @param uri the URI to load the image from
     */
    public void setImageUriAsync(Uri uri) {
        if (uri != null) {
            BitmapLoadingWorkerTask currentTask = mBitmapLoadingWorkerTask != null ? mBitmapLoadingWorkerTask.get() : null;
            if (currentTask != null) {
                // cancel previous loading (no check if the same URI because camera URI can be the same for different images)
                currentTask.cancel(true);
            }

            // either no existing task is working or we canceled it, need to load new URI
            clearImage(true);
            mCropOverlayView.setInitialCropWindowRect(null);
            mBitmapLoadingWorkerTask = new WeakReference<>(new BitmapLoadingWorkerTask(this, uri));
            mBitmapLoadingWorkerTask.get().execute();
            setProgressBarVisibility();
        }
    }

    /**
     * Clear the current image set for cropping.
     */
    public void clearImage() {
        clearImage(true);
        mCropOverlayView.setInitialCropWindowRect(null);
    }

    /**
     * Rotates image by the specified number of degrees clockwise.<br>
     * Cycles from 0 to 360 degrees.
     *
     * @param degrees Integer specifying the number of degrees to rotate.
     */
    public void rotateImage(int degrees) {
        if (mBitmap != null) {
            if (degrees % 90 == 0) {

                BitmapUtils.RECT.set(mCropOverlayView.getCropWindowRect());

                mImageMatrix.invert(mImageInverseMatrix);
                mImageInverseMatrix.mapRect(BitmapUtils.RECT);

                mZoom = 1;
                mZoomOffsetX = 0;
                mZoomOffsetY = 0;
                mDegreesRotated += degrees;
                mDegreesRotated = mDegreesRotated >= 0 ? mDegreesRotated % 360 : mDegreesRotated % 360 + 360;

                applyImageMatrix(getWidth(), getHeight(), true, false);

                mImageMatrix.mapRect(BitmapUtils.RECT);

                mCropOverlayView.resetCropOverlayView();
                mCropOverlayView.setCropWindowRect(BitmapUtils.RECT);
                applyImageMatrix(getWidth(), getHeight(), true, false);
                handleCropWindowChanged(false, false);

            } else {

                mDegreesRotated += degrees;
                mDegreesRotated = mDegreesRotated >= 0 ? mDegreesRotated % 360 : mDegreesRotated % 360 + 360;

                mZoom = 1;
                mZoomOffsetX = mZoomOffsetY = 0;
                mCropOverlayView.resetCropOverlayView();
                applyImageMatrix(getWidth(), getHeight(), true, false);
            }
        }
    }

    //region: Private methods

    /**
     * On complete of the async bitmap loading by {@link #setImageUriAsync(Uri)} set the result
     * to the widget if still relevant and call listener if set.
     *
     * @param result the result of bitmap loading
     */
    void onSetImageUriAsyncComplete(BitmapLoadingWorkerTask.Result result) {

        mBitmapLoadingWorkerTask = null;
        setProgressBarVisibility();

        if (result.error == null) {
            setBitmap(result.bitmap, true);
            mLoadedImageUri = result.uri;
            mLoadedSampleSize = result.loadSampleSize;
            mDegreesRotated = result.degreesRotated;
        }

        OnSetImageUriCompleteListener listener = mOnSetImageUriCompleteListener != null
                ? mOnSetImageUriCompleteListener.get() : null;
        if (listener != null) {
            listener.onSetImageUriComplete(this, result.uri, result.error);
        }
    }

    /**
     * On complete of the async bitmap cropping by {@link #getCroppedImageAsync()} call listener if set.
     *
     * @param result the result of bitmap cropping
     */
    void onImageCroppingAsyncComplete(BitmapCroppingWorkerTask.Result result) {

        mBitmapCroppingWorkerTask = null;
        setProgressBarVisibility();

        if (result.isSave) {
            OnSaveCroppedImageCompleteListener listener = mOnSaveCroppedImageCompleteListener != null
                    ? mOnSaveCroppedImageCompleteListener.get() : null;
            if (listener != null) {
                listener.onSaveCroppedImageComplete(this, result.uri, result.error);
            }
        } else {
            OnGetCroppedImageCompleteListener listener = mOnGetCroppedImageCompleteListener != null
                    ? mOnGetCroppedImageCompleteListener.get() : null;
            if (listener != null) {
                listener.onGetCroppedImageComplete(this, result.bitmap, result.error);
            }
        }
    }

    /**
     * Set the given bitmap to be used in for cropping<br>
     * Optionally clear full if the bitmap is new, or partial clear if the bitmap has been manipulated.
     */
    private void setBitmap(Bitmap bitmap, boolean clearFull) {
        if (mBitmap == null || !mBitmap.equals(bitmap)) {

            mImageView.clearAnimation();

            clearImage(clearFull);

            mBitmap = bitmap;
            mImageView.setImageBitmap(mBitmap);

            applyImageMatrix(getWidth(), getHeight(), true, false);

            if (mCropOverlayView != null) {
                mCropOverlayView.resetCropOverlayView();
                setCropOverlayVisibility();
            }
        }
    }

    public Bitmap getBitmap(){
        return this.mBitmap;
    }

    /**
     * Clear the current image set for cropping.<br>
     * Full clear will also clear the data of the set image like Uri or Resource id while partial clear
     * will only clear the bitmap and recycle if required.
     */
    private void clearImage(boolean full) {

        // if we allocated the bitmap, release it as fast as possible
        if (mBitmap != null && (mImageResource > 0 || mLoadedImageUri != null)) {
            mBitmap.recycle();
        }
        mBitmap = null;

        if (full) {
            // clean the loaded image flags for new image
            mImageResource = 0;
            mLoadedImageUri = null;
            mLoadedSampleSize = 1;
            mDegreesRotated = 0;
            mZoom = 1;
            mZoomOffsetX = 0;
            mZoomOffsetY = 0;
            mImageMatrix.reset();

            mImageView.setImageBitmap(null);

            setCropOverlayVisibility();
        }
    }

    /**
     * Gets the cropped image based on the current crop window.<br>
     * If (reqWidth,reqHeight) is given AND image is loaded from URI cropping will try to use sample size to fit in
     * the requested width and height down-sampling if possible - optimization to get best size to quality.<br>
     * The result will be invoked to listener set by {@link #setOnGetCroppedImageCompleteListener(OnGetCroppedImageCompleteListener)}.
     *
     * @param reqWidth optional: the width to downsample the cropped image to
     * @param reqHeight optional: the height to downsample the cropped image to
     * @param saveUri optional: to save the cropped image to
     * @param saveCompressFormat if saveUri is given, the given compression will be used for saving the image
     * @param saveCompressQuality if saveUri is given, the given quiality will be used for the compression.
     */
    public void startCropWorkerTask(int reqWidth, int reqHeight, Uri saveUri, Bitmap.CompressFormat saveCompressFormat, int saveCompressQuality) {

        mImageView.clearAnimation();

        BitmapCroppingWorkerTask currentTask = mBitmapCroppingWorkerTask != null ? mBitmapCroppingWorkerTask.get() : null;
        if (currentTask != null) {
            // cancel previous cropping
            currentTask.cancel(true);
        }

        int orgWidth = mBitmap.getWidth() * mLoadedSampleSize;
        int orgHeight = mBitmap.getHeight() * mLoadedSampleSize;
        if (mLoadedImageUri != null && mLoadedSampleSize > 1) {
            mBitmapCroppingWorkerTask = new WeakReference<>(new BitmapCroppingWorkerTask(this, mLoadedImageUri, getCropPoints(),
                    mDegreesRotated, orgWidth, orgHeight,
                    mCropOverlayView.isFixAspectRatio(), mCropOverlayView.getAspectRatioX(), mCropOverlayView.getAspectRatioY(),
                    reqWidth, reqHeight, saveUri, saveCompressFormat, saveCompressQuality));
        } else {
            mBitmapCroppingWorkerTask = new WeakReference<>(new BitmapCroppingWorkerTask(this, mBitmap, getCropPoints(), mDegreesRotated,
                    mCropOverlayView.isFixAspectRatio(), mCropOverlayView.getAspectRatioX(), mCropOverlayView.getAspectRatioY(), saveUri, saveCompressFormat, saveCompressQuality));
        }
        mBitmapCroppingWorkerTask.get().execute();
        setProgressBarVisibility();
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Bundle bundle = new Bundle();
        bundle.putParcelable("instanceState", super.onSaveInstanceState());
        bundle.putParcelable("LOADED_IMAGE_URI", mLoadedImageUri);
        bundle.putInt("LOADED_IMAGE_RESOURCE", mImageResource);
        if (mLoadedImageUri == null && mImageResource < 1) {
            bundle.putParcelable("SET_BITMAP", mBitmap);
        }
        if (mLoadedImageUri != null && mBitmap != null) {
            String key = UUID.randomUUID().toString();
            BitmapUtils.mStateBitmap = new Pair<>(key, new WeakReference<>(mBitmap));
            bundle.putString("LOADED_IMAGE_STATE_BITMAP_KEY", key);
        }
        if (mBitmapLoadingWorkerTask != null) {
            BitmapLoadingWorkerTask task = mBitmapLoadingWorkerTask.get();
            if (task != null) {
                bundle.putParcelable("LOADING_IMAGE_URI", task.getUri());
            }
        }
        bundle.putInt("LOADED_SAMPLE_SIZE", mLoadedSampleSize);
        bundle.putInt("DEGREES_ROTATED", mDegreesRotated);
        bundle.putParcelable("INITIAL_CROP_RECT", mCropOverlayView.getInitialCropWindowRect());

        BitmapUtils.RECT.set(mCropOverlayView.getCropWindowRect());

        mImageMatrix.invert(mImageInverseMatrix);
        mImageInverseMatrix.mapRect(BitmapUtils.RECT);

        bundle.putParcelable("CROP_WINDOW_RECT", BitmapUtils.RECT);
        bundle.putString("CROP_SHAPE", mCropOverlayView.getCropShape().name());
        bundle.putBoolean("CROP_AUTO_ZOOM_ENABLED", mAutoZoomEnabled);
        bundle.putInt("CROP_MAX_ZOOM", mMaxZoom);

        return bundle;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (state instanceof Bundle) {
            Bundle bundle = (Bundle) state;

            Bitmap bitmap = null;
            Uri uri = bundle.getParcelable("LOADED_IMAGE_URI");
            if (uri != null) {
                String key = bundle.getString("LOADED_IMAGE_STATE_BITMAP_KEY");
                if (key != null) {
                    Bitmap stateBitmap = BitmapUtils.mStateBitmap != null && BitmapUtils.mStateBitmap.first.equals(key)
                            ? BitmapUtils.mStateBitmap.second.get() : null;
                    if (stateBitmap != null && !stateBitmap.isRecycled()) {
                        BitmapUtils.mStateBitmap = null;
                        setBitmap(stateBitmap, true);
                        mLoadedImageUri = uri;
                        mLoadedSampleSize = bundle.getInt("LOADED_SAMPLE_SIZE");
                    }
                }
                if (mLoadedImageUri == null) {
                    setImageUriAsync(uri);
                }

            } else {
                int resId = bundle.getInt("LOADED_IMAGE_RESOURCE");
                if (resId > 0) {
                    setImageResource(resId);
                } else {
                    bitmap = bundle.getParcelable("SET_BITMAP");
                    if (bitmap != null) {
                        setBitmap(bitmap, true);
                    } else {
                        uri = bundle.getParcelable("LOADING_IMAGE_URI");
                        if (uri != null) {
                            setImageUriAsync(uri);
                        }
                    }
                }
            }

            mDegreesRotated = bundle.getInt("DEGREES_ROTATED");

            mCropOverlayView.setInitialCropWindowRect((Rect) bundle.getParcelable("INITIAL_CROP_RECT"));

            mRestoreCropWindowRect = bundle.getParcelable("CROP_WINDOW_RECT");

            mCropOverlayView.setCropShape(CropShape.valueOf(bundle.getString("CROP_SHAPE")));

            mAutoZoomEnabled = bundle.getBoolean("CROP_AUTO_ZOOM_ENABLED");
            mMaxZoom = bundle.getInt("CROP_MAX_ZOOM");

            super.onRestoreInstanceState(bundle.getParcelable("instanceState"));
        } else {
            super.onRestoreInstanceState(state);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (mBitmap != null) {

            // Bypasses a baffling bug when used within a ScrollView, where heightSize is set to 0.
            if (heightSize == 0) {
                heightSize = mBitmap.getHeight();
            }

            int desiredWidth;
            int desiredHeight;

            double viewToBitmapWidthRatio = Double.POSITIVE_INFINITY;
            double viewToBitmapHeightRatio = Double.POSITIVE_INFINITY;

            // Checks if either width or height needs to be fixed
            if (widthSize < mBitmap.getWidth()) {
                viewToBitmapWidthRatio = (double) widthSize / (double) mBitmap.getWidth();
            }
            if (heightSize < mBitmap.getHeight()) {
                viewToBitmapHeightRatio = (double) heightSize / (double) mBitmap.getHeight();
            }

            // If either needs to be fixed, choose smallest ratio and calculate from there
            if (viewToBitmapWidthRatio != Double.POSITIVE_INFINITY || viewToBitmapHeightRatio != Double.POSITIVE_INFINITY) {
                if (viewToBitmapWidthRatio <= viewToBitmapHeightRatio) {
                    desiredWidth = widthSize;
                    desiredHeight = (int) (mBitmap.getHeight() * viewToBitmapWidthRatio);
                } else {
                    desiredHeight = heightSize;
                    desiredWidth = (int) (mBitmap.getWidth() * viewToBitmapHeightRatio);
                }
            } else {
                // Otherwise, the picture is within frame layout bounds. Desired width is simply picture size
                desiredWidth = mBitmap.getWidth();
                desiredHeight = mBitmap.getHeight();
            }

            int width = getOnMeasureSpec(widthMode, widthSize, desiredWidth);
            int height = getOnMeasureSpec(heightMode, heightSize, desiredHeight);

            mLayoutWidth = width;
            mLayoutHeight = height;

            setMeasuredDimension(mLayoutWidth, mLayoutHeight);

        } else {
            setMeasuredDimension(widthSize, heightSize);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

        super.onLayout(changed, l, t, r, b);

        if (mLayoutWidth > 0 && mLayoutHeight > 0) {
            // Gets original parameters, and creates the new parameters
            ViewGroup.LayoutParams origParams = this.getLayoutParams();
            origParams.width = mLayoutWidth;
            origParams.height = mLayoutHeight;
            setLayoutParams(origParams);

            if (mBitmap != null) {
                applyImageMatrix(r - l, b - t, false, false);

                // after state restore we want to restore the window crop, possible only after widget size is known
                if (mBitmap != null && mRestoreCropWindowRect != null) {
                    mImageMatrix.mapRect(mRestoreCropWindowRect);
                    mCropOverlayView.setCropWindowRect(mRestoreCropWindowRect);
                    mRestoreCropWindowRect = null;
                    handleCropWindowChanged(false, false);
                }
            } else {
                updateBitmapRect(BitmapUtils.EMPTY_RECT_F);
            }
        } else {
            updateBitmapRect(BitmapUtils.EMPTY_RECT_F);
        }
    }

    /**
     * Handle crop window change to:<br>
     * 1. Execute auto-zoom-in/out depending on the area covered of cropping window relative to the
     * available view area.<br>
     * 2. Slide the zoomed sub-area if the cropping window is outside of the visible view sub-area.<br>
     *
     * @param inProgress is the crop window change is still in progress by the user
     * @param animate if to animate the change to the image matrix, or set it directly
     */
    private void handleCropWindowChanged(boolean inProgress, boolean animate) {
        int width = getWidth();
        int height = getHeight();
        if (mBitmap != null && width > 0 && height > 0) {

            RectF cropRect = mCropOverlayView.getCropWindowRect();
            if (inProgress) {
                if (cropRect.left < 0 || cropRect.top < 0 || cropRect.right > width || cropRect.bottom > height) {
                    applyImageMatrix(width, height, false, false);
                }
            } else if (mAutoZoomEnabled || mZoom > 1) {
                float newZoom = 0;
                // keep the cropping window covered area to 50%-65% of zoomed sub-area
                if (mZoom < mMaxZoom && cropRect.width() < width * 0.5f && cropRect.height() < height * 0.5f) {
                    newZoom = Math.min(mMaxZoom, Math.min(width / (cropRect.width() / mZoom / 0.64f), height / (cropRect.height() / mZoom / 0.64f)));
                }
                if (mZoom > 1 && (cropRect.width() > width * 0.65f || cropRect.height() > height * 0.65f)) {
                    newZoom = Math.max(1, Math.min(width / (cropRect.width() / mZoom / 0.51f), height / (cropRect.height() / mZoom / 0.51f)));
                }
                if (!mAutoZoomEnabled) {
                    newZoom = 1;
                }

                if (newZoom > 0 && newZoom != mZoom) {
                    if (animate) {
                        if (mAnimation == null) {
                            // lazy create animation single instance
                            mAnimation = new CropImageAnimation(mImageView, mCropOverlayView);
                        }
                        // set the state for animation to start from
                        mAnimation.setStartState(mImageRect, mImageMatrix);
                    }

                    updateCropRectByZoomChange(newZoom / mZoom);
                    mZoom = newZoom;

                    applyImageMatrix(width, height, true, animate);
                }
            }
        }
    }

    /**
     * Adjust the given crop window rectangle by the change in zoom, need to update the location and size
     * of the crop rectangle to cover the same area in new zoom level.
     */
    private void updateCropRectByZoomChange(float zoomChange) {
        RectF cropRect = mCropOverlayView.getCropWindowRect();
        float xCenterOffset = getWidth() / 2 - cropRect.centerX();
        float yCenterOffset = getHeight() / 2 - cropRect.centerY();
        cropRect.offset(xCenterOffset - xCenterOffset * zoomChange, yCenterOffset - yCenterOffset * zoomChange);
        cropRect.inset((cropRect.width() - cropRect.width() * zoomChange) / 2f, (cropRect.height() - cropRect.height() * zoomChange) / 2f);
        mCropOverlayView.setCropWindowRect(cropRect);
    }

    /**
     * Apply matrix to handle the image inside the image view.
     *
     * @param width the width of the image view
     * @param height the height of the image view
     */
    private void applyImageMatrix(float width, float height, boolean center, boolean animate) {
        if (mBitmap != null && width > 0 && height > 0) {

            mImageMatrix.reset();
            mImageRect.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());

            // move the image to the center of the image view first so we can manipulate it from there
            mImageMatrix.postTranslate((width - mImageRect.width()) / 2, (height - mImageRect.height()) / 2);
            mapImageRectangleByImageMatrix(mImageRect);

            // rotate the image the required degrees from center of image
            if (mDegreesRotated > 0) {
                mImageMatrix.postRotate(mDegreesRotated, mImageRect.centerX(), mImageRect.centerY());
                mapImageRectangleByImageMatrix(mImageRect);
            }

            // scale the image to the image view, image rect transformed to know new width/height
            float scale = Math.min(width / mImageRect.width(), height / mImageRect.height());
            if (mScaleType == ScaleType.FIT_CENTER || (mScaleType == ScaleType.CENTER_INSIDE && scale < 1) || (scale > 1 && mAutoZoomEnabled)) {
                mImageMatrix.postScale(scale, scale, mImageRect.centerX(), mImageRect.centerY());
                mapImageRectangleByImageMatrix(mImageRect);
            }

            // scale by the current zoom level
            mImageMatrix.postScale(mZoom, mZoom, mImageRect.centerX(), mImageRect.centerY());
            mapImageRectangleByImageMatrix(mImageRect);

            RectF cropRect = mCropOverlayView.getCropWindowRect();

            // reset the crop window offset so we can update it to required value
            cropRect.offset(-mZoomOffsetX * mZoom, -mZoomOffsetY * mZoom);

            if (center) {
                // set the zoomed area to be as to the center of cropping window as possible
                mZoomOffsetX = width > mImageRect.width() ? 0
                        : Math.max(Math.min(width / 2 - cropRect.centerX(), -mImageRect.left), getWidth() - mImageRect.right) / mZoom;
                mZoomOffsetY = height > mImageRect.height() ? 0
                        : Math.max(Math.min(height / 2 - cropRect.centerY(), -mImageRect.top), getHeight() - mImageRect.bottom) / mZoom;
            } else {
                // adjust the zoomed area so the crop window rectangle will be inside the area in case it was moved outside
                mZoomOffsetX = Math.min(Math.max(mZoomOffsetX * mZoom, -cropRect.left), -cropRect.right + width) / mZoom;
                mZoomOffsetY = Math.min(Math.max(mZoomOffsetY * mZoom, -cropRect.top), -cropRect.bottom + height) / mZoom;
            }

            // apply to zoom offset translate and update the crop rectangle to offset correctly
            mImageMatrix.postTranslate(mZoomOffsetX * mZoom, mZoomOffsetY * mZoom);
            cropRect.offset(mZoomOffsetX * mZoom, mZoomOffsetY * mZoom);
            mCropOverlayView.setCropWindowRect(cropRect);
            mapImageRectangleByImageMatrix(mImageRect);

            // set matrix to apply
            if (animate) {
                // set the state for animation to end in, start animation now
                mAnimation.setEndState(mImageRect, mImageMatrix);
                mImageView.startAnimation(mAnimation);
            } else {
                mImageView.setImageMatrix(mImageMatrix);
            }

            // update the image rectangle in the crop overlay
            updateBitmapRect(mImageRect);
        }
    }

    /**
     * Adjust the given image rectangle by image transformation matrix to know the final rectangle of the image.<br>
     * To get the proper rectangle it must be first reset to orginal image rectangle.
     */
    private void mapImageRectangleByImageMatrix(RectF imgRect) {
        imgRect.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
        mImageMatrix.mapRect(imgRect);
    }

    /**
     * Determines the specs for the onMeasure function. Calculates the width or height
     * depending on the mode.
     *
     * @param measureSpecMode The mode of the measured width or height.
     * @param measureSpecSize The size of the measured width or height.
     * @param desiredSize The desired size of the measured width or height.
     * @return The final size of the width or height.
     */
    private static int getOnMeasureSpec(int measureSpecMode, int measureSpecSize, int desiredSize) {

        // Measure Width
        int spec;
        if (measureSpecMode == MeasureSpec.EXACTLY) {
            // Must be this size
            spec = measureSpecSize;
        } else if (measureSpecMode == MeasureSpec.AT_MOST) {
            // Can't be bigger than...; match_parent value
            spec = Math.min(desiredSize, measureSpecSize);
        } else {
            // Be whatever you want; wrap_content
            spec = desiredSize;
        }

        return spec;
    }

    /**
     * Set visibility of crop overlay to hide it when there is no image or specificly set by client.
     */
    private void setCropOverlayVisibility() {
        if (mCropOverlayView != null) {
            mCropOverlayView.setVisibility(mShowCropOverlay && mBitmap != null ? VISIBLE : INVISIBLE);
        }
    }

    /**
     * Set visibility of progress bar when async loading/cropping is in process and show is enabled.
     */
    private void setProgressBarVisibility() {
        boolean visible = mShowProgressBar &&
                (mBitmap == null && mBitmapLoadingWorkerTask != null || mBitmapCroppingWorkerTask != null);
        mProgressBar.setVisibility(visible ? VISIBLE : INVISIBLE);
    }

    /**
     * Update the scale factor between the actual image bitmap and the shown image.<br>
     */
    private void updateBitmapRect(RectF bitmapRect) {
        if (mBitmap != null && bitmapRect.width() > 0 && bitmapRect.height() > 0) {

            // Get the scale factor between the actual Bitmap dimensions and the displayed dimensions for width/height.
            float scaleFactorWidth = mBitmap.getWidth() * mLoadedSampleSize / bitmapRect.width();
            float scaleFactorHeight = mBitmap.getHeight() * mLoadedSampleSize / bitmapRect.height();
            mCropOverlayView.setCropWindowLimits(getWidth(), getHeight(), scaleFactorWidth, scaleFactorHeight);
        }

        // set the bitmap rectangle and update the crop window after scale factor is set
        mCropOverlayView.setBitmapRect(bitmapRect, getWidth(), getHeight());
    }
    //endregion

    //region: Inner class: CropShape

    /**
     * The possible cropping area shape.
     */
    public enum CropShape {
        RECTANGLE,
        OVAL
    }
    //endregion

    //region: Inner class: ScaleType

    /**
     * Options for scaling the bounds of cropping image to the bounds of Crop Image View.<br>
     * Note: Some options are affected by auto-zoom, if enabled.
     */
    public enum ScaleType {

        /**
         * Scale the image uniformly (maintain the image's aspect ratio) to fit in crop image view.<br>
         * The largest dimension will be equals to crop image viee and the second dimension will be smaller.
         */
        FIT_CENTER,

        /**
         * Center the image in the view, but perform no scaling.<br>
         * Note: If auto-zoom is enabled and the source image is smaller than crop image view then it will be
         * scaled uniformly to fit the crop image view.
         */
        CENTER,

        /**
         * Scale the image uniformly (maintain the image's aspect ratio) so that both
         * dimensions (width and height) of the image will be equal to or <b>larger</b> than the
         * corresponding dimension of the view (minus padding).<br>
         * The image is then centered in the view.
         */
        CENTER_CROP,

        /**
         * Scale the image uniformly (maintain the image's aspect ratio) so that both
         * dimensions (width and height) of the image will be equal to or <b>less</b> than the
         * corresponding dimension of the view (minus padding).<br>
         * The image is then centered in the view.<br>
         * Note: If auto-zoom is enabled and the source image is smaller than crop image view then it will be
         * scaled uniformly to fit the crop image view.
         */
        CENTER_INSIDE
    }
    //endregion

    //region: Inner class: Guidelines

    /**
     * The possible guidelines showing types.
     */
    public enum Guidelines {
        /**
         * Never show
         */
        OFF,

        /**
         * Show when crop move action is live
         */
        ON_TOUCH,

        /**
         * Always show
         */
        ON
    }
    //endregion

    //region: Inner class: OnSetImageUriCompleteListener

    /**
     * Interface definition for a callback to be invoked when image async loading is complete.
     */
    public interface OnSetImageUriCompleteListener {

        /**
         * Called when a crop image view has completed loading image for cropping.<br>
         * If loading failed error parameter will contain the error.
         *
         * @param view The crop image view that loading of image was complete.
         * @param uri the URI of the image that was loading
         * @param error if error occurred during loading will contain the error, otherwise null.
         */
        void onSetImageUriComplete(CropImageView view, Uri uri, Exception error);
    }
    //endregion

    //region: Inner class: OnGetCroppedImageCompleteListener

    /**
     * Interface definition for a callback to be invoked when image async cropping is complete.
     */
    public interface OnGetCroppedImageCompleteListener {

        /**
         * Called when a crop image view has completed cropping image.<br>
         * If cropping failed error parameter will contain the error.
         *
         * @param view The crop image view that cropping of image was complete.
         * @param bitmap the cropped image bitmap (null if failed)
         * @param error if error occurred during cropping will contain the error, otherwise null.
         */
        void onGetCroppedImageComplete(CropImageView view, Bitmap bitmap, Exception error);
    }
    //endregion

    //region: Inner class: OnSaveCroppedImageCompleteListener

    /**
     * Interface definition for a callback to be invoked when image async cropping is complete.
     */
    public interface OnSaveCroppedImageCompleteListener {

        /**
         * Called when a crop image view has completed cropping image.<br>
         * If cropping failed error parameter will contain the error.
         *
         * @param view The crop image view that cropping of image was complete.
         * @param uri the cropped image uri (null if failed)
         * @param error if error occurred during cropping will contain the error, otherwise null.
         */
        void onSaveCroppedImageComplete(CropImageView view, Uri uri, Exception error);
    }
    //endregion
}
ui/src/main/java/net/crofis/ui/custom/actionitem/ActionItem.java
public class ActionItem {
    private Drawable icon;
    private String title;
    private ActionItemClickListener clickListener;

    public ActionItem(Drawable icon,String title, ActionItemClickListener listener){
        this.icon = icon;
        this.title = title;
        this.clickListener = listener;
    }

    public ActionItemClickListener getClickListener() {
        return clickListener;
    }

    public void setOnClickListener(ActionItemClickListener clickListener) {
        this.clickListener = clickListener;
    }

    /**
     * Get the icon of the action.
     * @return
     */
    public Drawable getIcon() {
        return icon;
    }

    /**
     * Set the icon of the action
     * @param icon
     */
    public void setIcon(Drawable icon) {
        this.icon = icon;
    }

    /**
     * Get the title.
     * @return
     */
    public String getTitle() {
        return title;
    }

    /**
     * Set the action title.
     * @param title
     */
    public void setTitle(String title) {
        this.title = title;
    }


    /**
     * getView() is a method that is used only by sub-classes. It will always return null by the super class.
     * Example on how to override:
     *
     *   //The following code snippet, will use the default item but it will be designed like iOS.
     *
     *   View convertView =  LayoutInflater.from(context).inflate(R.layout.ui_general_action, null, false);
     *   TextView title = (TextView) convertView.findViewById(R.id.action_title);
     *   title.setText(getTitle());
     *   title.setGravity(Gravity.CENTER);
     *   title.setTextColor(context.getResources().getColor(R.color.blue));
     *   if (false)((ImageView) convertView.findViewById(R.id.action_icon)).setImageDrawable(getIcon());
     *   else convertView.findViewById(R.id.action_icon).setVisibility(View.GONE);
     *   return convertView;
     *
     *
     * @param context - The context that will be given when the method is called within the ListView Adapter.
     * @param showActionIcons - The setting that was set in the ActionDialog class.
     * @return The custom view you wish to use,return null if you want the default item.
     */
    public View getView(Context context,boolean showActionIcons){
        return null;
    }
}
ui/src/main/java/net/crofis/ui/dialog/BaseAlertDialog.java
public abstract class BaseAlertDialog {
     public final static String TAG = "CrofisAlertDialog";

    /**Application Context**/
    Context context;

    /**The View of the dialog**/
    View dialogView;

    /**The Root AlertDialog**/
    AlertDialog dialog;

    public abstract void show();

    public abstract void dismiss();

    public abstract AlertDialog getDialog();

    public abstract View getDialogView();

    public abstract Context getContext();

    public interface OnClickListener {

        void onClick(View v, BaseAlertDialog dialog);
    }
}


Question: import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.SurfaceHolder;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.LinearInterpolator;
import android.view.animation.ScaleAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.SeekBar;
import com.github.clans.fab.FloatingActionButton;
import net.crofis.ui.R;
import net.crofis.ui.custom.PreviewSurfaceView;
import net.crofis.ui.custom.actionitem.ActionItem;
import net.crofis.ui.custom.actionitem.ActionItemClickListener;
import net.crofis.ui.custom.actionitem.UIAlertAction;
import net.crofis.ui.custom.cropper.CropImageView;
import net.crofis.ui.custom.seekbar.SeekBarCompat;
import net.crofis.ui.dialog.ActionDialog;
import net.crofis.ui.dialog.BaseAlertDialog;
import net.crofis.ui.dialog.DialogManager;
import net.crofis.ui.dialog.InfoDialog;
import net.crofis.ui.dialog.LoadingDialog;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
                break;
            case 1:
                hidePreview(true);
                imageTaken = null;
                RESULT_CODE = 0;
                break;
            case 2:
                findViewById(R.id.camera_view).setVisibility(View.VISIBLE);
                findViewById(R.id.crop_view_layout).setVisibility(View.GONE);
                if(!SET_CROP_OPTIONAL) {
                    hidePreview(true);
                    imageTaken = null;
                    RESULT_CODE = 0;
                    CURRENT_PREVIEW_STATUS = 0;
                }
                else CURRENT_PREVIEW_STATUS = 1;
                break;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);

        /**
         * Initialize camera with custom parameters.
         */
        Intent intent = getIntent();
        DISPLAY_FLASH_TOGGLE = intent.getBooleanExtra(FLAG_DISPLAY_FLASH_TOGGLE,true);
        DISPLAY_SWITCH_CAM = intent.getBooleanExtra(FLAG_DISPLAY_SWITCH_CAM,true);
        SHOW_ZOOM_SEEKBAR = intent.getBooleanExtra(FLAG_SHOW_ZOOM_SEEKBAR,true);
        ALLOW_ZOOM_GESTURE = intent.getBooleanExtra(FLAG_ALLOW_ZOOM_GESTURE, true);
        ALLOW_ROTATION_ANIMATION = intent.getBooleanExtra(FLAG_ALLOW_ROTATION_ANIMATION,true);
        SET_CROP_OPTIONAL = intent.getBooleanExtra(FLAG_SET_CROP_OPTIONAL,false);
        //if(!SET_CROP_OPTIONAL) ALLOW_IMAGE_CROP = intent.getBooleanExtra(FLAG_ALLOW_CROP,false);
        SAVE_FILE_TO_STORAGE = intent.getBooleanExtra(FLAG_SAVE_TO_STORAGE,false);
        if(SAVE_FILE_TO_STORAGE) RETURN_DATA_AS_BYTE_ARRAY = intent.getBooleanExtra(FLAG_RETURN_DATA_AS_BYTE_ARRAY,true);
        CROP_ASPECT_RATIO = intent.getIntExtra(FLAG_SET_CROP_ASPECT_RATIO,-1);
        if(CROP_ASPECT_RATIO == -2) {
            cropAspectX = intent.getIntExtra("cropAspectX",-1);
            cropAspectY = intent.getIntExtra("cropAspectY",-1);
        }
        cam_view = intent.getIntExtra(FLAG_LOAD_CAM,BACK_CAM_ID);

        //VALIDATE USER INPUT - CAMERA MUST BE 0 OR 1
        if(cam_view!=BACK_CAM_ID||cam_view!=FRONT_CAM_ID) cam_view =BACK_CAM_ID;

        /**
         * Initialize sensors.
         */
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        magnetometer = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

        surfaceView = (PreviewSurfaceView) findViewById(R.id.surfaceView);
        detector = new ScaleGestureDetector(this, new ScaleListener());
        surfaceHolder = surfaceView.getHolder();
        zoomBar = (SeekBarCompat) findViewById(R.id.seekBar);
        surfaceCover = findViewById(R.id.surfaceCover);

        /**Setup SeekBar Change Listener**/
        zoomBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            /**
             * Is called whenever the progress of the seekbar has changed.
             * @param seekBar is the seekbar view
             * @param i is the progress
             * @param b if the progress was changed programmatically or by the user. (true = user, false = programmatically)
             */
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                zoom(i);
            }

            /**
             * This method is called whenever the user is interacting with the seekbar.
             * When called, modify the scaleFactor and remove the Auto Hide callback
             * (so the bar won't disappear while the user is interacting with it).
             *
             * @param seekBar is the seekbar view.
             */
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                int i = seekBar.getProgress();
                scaleFactor = (i/(100/MAX_ZOOM)) + MIN_ZOOM;
                zoomHandler.removeCallbacks(hideSeekbar);
            }

            /**
             * Method is called when user is done interacting with seekbar.
             * When called the zoomHandler will be activated again in order to hide the seekbar again.
             * @param seekBar
             */
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                zoomHandler.postDelayed(hideSeekbar,HIDE_DELAY);

            }
        });


        /**
         * Initialize main intractable views.
         */
        captureButton = (ImageView) findViewById(R.id.camera);
        confirm = (FloatingActionButton) findViewById(R.id.pos);
        cancel = (FloatingActionButton) findViewById(R.id.neg);
        flashToggleButton = (ImageView) findViewById(R.id.toggleFlash);
        switchCamButton= (ImageView) findViewById(R.id.toggleCam);
        previewIv = (ImageView) findViewById(R.id.preview);
        cropImageButton = (ImageView) findViewById(R.id.cropImage);

        cropImageButton.setOnClickListener(new View.OnClickListener() {

            Bitmap output;
            @Override
            public void onClick(View v) {
                CURRENT_PREVIEW_STATUS = 2;
                if(cam_view == 1) output = codec(imageTaken, Bitmap.CompressFormat.JPEG,100);
                else output = imageTaken;
Answer:                 final CropImageView imageCropper = new CropImageView(CameraActivity.this);