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?