0

My app has a captcha to prevent abuse of certain features. It seems to work fine mostly but crashes for some users with IllegalArgumentException.

Here's my code:

public class CaptchaImageView extends AppCompatImageView {
    private CaptchaGenerator.Captcha generatedCaptcha;
    private int captchaLength = 6;
    private int captchaType = CaptchaGenerator.NUMBERS;
    private int width, height;
    private boolean isDot;
    private boolean isRedraw;
    private boolean isItalic = true;

    public CaptchaImageView(Context context) {
        super(context);
    }

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

    }

    private void draw(int width, int height) {
        generatedCaptcha = CaptchaGenerator.regenerate(width, height, captchaLength, captchaType, isDot, isItalic);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int desiredWidth = 300;
        int desiredHeight = 50;

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

        int width;
        int height;

        //Measure Width
        if (widthMode == MeasureSpec.EXACTLY) {
            //Must be this size
            width = widthSize;
        } else if (widthMode == MeasureSpec.AT_MOST) {
            //Can't be bigger than...
            width = Math.min(desiredWidth, widthSize);
        } else {
            //Be whatever you want
            width = desiredWidth;
        }

        //Measure Height
        if (heightMode == MeasureSpec.EXACTLY) {
            //Must be this size
            height = heightSize;
        } else if (heightMode == MeasureSpec.AT_MOST) {
            //Can't be bigger than...
            height = Math.min(desiredHeight, heightSize);
        } else {
            //Be whatever you want
            height = desiredHeight;
        }

        //MUST CALL THIS
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
    }

    public String getCaptchaCode() {
        return generatedCaptcha.getCaptchaCode();
    }
    
    public void regenerate() {
        reDraw();
    }
    
    public void setCaptchaType(int type) {
        captchaType = type;
    }
    
    private void reDraw() {
        post(() -> {
            if (width != 0 && height != 0) {
                draw(width, height);
                setImageBitmap(generatedCaptcha.getBitmap());
            }
        });

    }

    public void setIsDotNeeded(boolean isNeeded) {
        isDot = isNeeded;
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (!isRedraw) {
            reDraw();
            isRedraw = true;
        }

    }

    public static class CaptchaGenerator {
        public static final int ALPHABETS = 1, NUMBERS = 2, BOTH = 3;

        private static Captcha regenerate(int width, int height, int length, int type, boolean isDot, boolean isItalic) {
            Paint border = new Paint();
            border.setStyle(Paint.Style.STROKE);
            border.setColor(Color.parseColor("#CCCCCC"));
            Paint paint = new Paint();
            paint.setColor(Color.BLACK);
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
            if (isDot)
                paint.setTypeface(Typeface.DEFAULT_BOLD);
            else
                paint.setTypeface(Typeface.MONOSPACE);
            Bitmap bitMap = Bitmap.createBitmap(width,
                    height,
                    Bitmap.Config.ARGB_8888);
            Canvas canvas = new Canvas(bitMap);
            canvas.drawColor(Color.parseColor("#F7F7FF"));
            int textX = generateRandomInt(width - ((width / 6) * 4), width / 3);
            int textY = generateRandomInt(height - ((height / 3)), height - (height / 4));
            String generatedText = drawRandomText(canvas, paint, textX, textY, length, type, isDot, isItalic);
            if (isDot) {
                canvas.drawLine(textX, textY - generateRandomInt(7, 10), textX + (length * 33), textY - generateRandomInt(5, 10), paint);
                canvas.drawLine(textX, textY - generateRandomInt(7, 10), textX + (length * 33), textY - generateRandomInt(5, 10), paint);
            } else {
                canvas.drawLine(textX, textY - generateRandomInt(7, 10), textX + (length * 23), textY - generateRandomInt(5, 10), paint);
                canvas.drawLine(textX, textY - generateRandomInt(7, 10), textX + (length * 23), textY - generateRandomInt(5, 10), paint);
            }
            canvas.drawRect(0, 0, width - 1, height - 1, border);
            if (isDot)
                makeDots(bitMap, width, height, textX, textY);
            return (new Captcha(generatedText, bitMap));
        }

        private static void makeDots(Bitmap bitMap, int width, int height, int textX, int textY) {
            int white = -526337;
            int black = -16777216;
            Random random = new Random();
            for (int x = 0; x < width; x++) {
                for (int y = 0; y < height; y++) {
                    int pixel = bitMap.getPixel(x, y);
                    if (pixel == white) {
                        pixel = (random.nextBoolean()) ? black : white;
                    }
                    bitMap.setPixel(x, y, pixel);
                }
            }
        }

        private static String drawRandomText(Canvas canvas, Paint paint, int textX, int textY, int length, int type, boolean isDot, boolean isItalic) {
            String generatedCaptcha = "";
            int[] scewRange = {-1, 1};
            int[] textSizeRange = {90, 90, 90, 90};
            Random random = new Random();
            paint.setTextAlign(Paint.Align.CENTER);
            paint.setTextSkewX((isItalic) ? scewRange[random.nextInt(scewRange.length)] : 0);
            for (int index = 0; index < length; index++) {
                String temp = generateRandomText(type);
                generatedCaptcha = generatedCaptcha + temp;
                paint.setTextSize(textSizeRange[random.nextInt(textSizeRange.length)]);
                if (isDot)
                    canvas.drawText(temp, textX + (index * 50), textY, paint);
                else
                    canvas.drawText(temp, textX + (index * 40), textY, paint);
            }
            return generatedCaptcha;
        }

        private static String generateRandomText(int type) {
            String[] numbers = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
            String[] alphabets = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"
                    , "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
            Random random = new Random();
            Random mixedRandom = new Random();
            String temp;
            if (type == ALPHABETS)
                temp = alphabets[random.nextInt(alphabets.length)];
            else if (type == NUMBERS)
                temp = numbers[random.nextInt(numbers.length)];
            else
                temp = (mixedRandom.nextBoolean()) ? (alphabets[random.nextInt(alphabets.length)]) : (numbers[random.nextInt(numbers.length)]);
            return temp;
        }

        private static int generateRandomInt(int min, int max) {
            Random rand = new Random();
            return rand.nextInt((max - min) + 1) + min;
        }

        private static class Captcha {
            private String captchaCode;
            private Bitmap bitmap;

            Captcha(String captchaCode, Bitmap bitmap) {
                this.captchaCode = captchaCode;
                this.bitmap = bitmap;
            }

            String getCaptchaCode() {
                return captchaCode;
            }

            Bitmap getBitmap() {
                return bitmap;
            }
        }

    }
}

Here's where the crash is:

 private static int generateRandomInt(int min, int max) {
            Random rand = new Random();
            return rand.nextInt((max - min) + 1) + min;
        }

I'm not able to figure out what's wrong as it seems to be working on most devices but a handful of users are affected, so can someone tell me what flaw my code has or how I can prevent this crash?

Zain
  • 37,492
  • 7
  • 60
  • 84
Steven Rõgrê
  • 165
  • 1
  • 13
  • 2
    Really rethink that line `generateRandomInt(width - ((width / 6) * 4), width / 3)` and check the duplicate: [Division of integers in Java](//stackoverflow.com/q/7220681). – Tom Jun 08 '21 at 19:57
  • 1
    @Tom I've written it that way to bring the canvas text in the middle so do you have any suggestions? And yeah, seems like this is the line causing the issue. – Steven Rõgrê Jun 08 '21 at 20:04
  • 1
    @Tom ok I fixed it, it was my math error. Thanks for the help! – Steven Rõgrê Jun 08 '21 at 20:23

0 Answers0