Implement Captcha in Android

    Captcha is one of the authentication methods that you are not a robot. Through Wikipedia, a captcha is defined as:
(a backronym for "Completely Automated Public Turing test to tell Computers and Humans Apart") is a type of challenge-response test used in computing to determine whether or not the user is human.
    In web development, this topic is very popular and implement it in a web project is so simple. But in mobile programming in general and Android in particular, implementing captcha is not easy.
    Through this post, I will present the way to build a "captcha bitmap image" which can help you in developing authentication screens.

Create text and math captcha classes

    There are 2 popular types of captcha: text and math operator. We'll create theme and display them as Bitmap. Now, please copy these 3 classes that define the "Bitmap captcha" to your project:
Captcha.java
package info.devexchanges.androidcaptcha;

import java.util.List;
import java.util.Random;

import android.graphics.Bitmap;
import android.graphics.Color;

public abstract class Captcha {
 protected Bitmap image;
 protected String answer = "";
 private int width;
 protected int height;
 protected int x = 0;
 protected int y = 0;
 protected static List usedColors;
 
 protected abstract Bitmap image();

 public static int color(){
     Random r = new Random();
     int number;
     do{
      number = r.nextInt(9);
     }while(usedColors.contains(number));
     usedColors.add(number);
     switch(number){
      case 0: return Color.BLACK;
      case 1: return Color.BLUE;
      case 2: return Color.CYAN;
      case 3: return Color.DKGRAY;
      case 4: return Color.GRAY;
      case 5: return Color.GREEN;
      case 6: return Color.MAGENTA;
      case 7: return Color.RED;
      case 8: return Color.YELLOW;
      case 9: return Color.WHITE;
      default: return Color.WHITE;
     }
    }
    
    public int getWidth(){
     return this.width;
    }
    
    public void setWidth(int width){
     if(width > 0 && width < 10000){
      this.width = width;
     }else{
      this.width = 300;
     }
    }
    
    public int getHeight(){
     return this.height;
    }
    
    public void setHeight(int height){
     if(height > 0 && height < 10000){
      this.height = height;
     }else{
      this.height = 100;
     }
    }
    
 public Bitmap getImage() {
  return this.image;
 }

 public boolean checkAnswer(String ans) {
  return ans.equals(this.answer);
 }
}
TextCaptcha.java
package info.devexchanges.androidcaptcha;

import java.io.CharArrayWriter;
import java.util.ArrayList;
import java.util.Random;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.Bitmap.Config;

public class TextCaptcha extends Captcha {

    protected TextOptions options;
    private int wordLength;
    private char mCh;

    public enum TextOptions {
        UPPERCASE_ONLY,
        LOWERCASE_ONLY,
        NUMBERS_ONLY,
        LETTERS_ONLY,
        NUMBERS_AND_LETTERS
    }

    public TextCaptcha(int wordLength, TextOptions opt) {
        new TextCaptcha(0, 0, wordLength, opt);
    }

    public TextCaptcha(int width, int height, int wordLength, TextOptions opt) {
        setHeight(height);
        setWidth(width);
        this.options = opt;
        usedColors = new ArrayList<>();
        this.wordLength = wordLength;
        this.image = image();
    }

    @Override
    protected Bitmap image() {
        LinearGradient gradient = new LinearGradient(0, 0, getWidth() / this.wordLength, getHeight() / 2, color(), color(), Shader.TileMode.MIRROR);
        Paint p = new Paint();
        p.setDither(true);
        p.setShader(gradient);
        Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888);
        Canvas c = new Canvas(bitmap);
        c.drawRect(0, 0, getWidth(), getHeight(), p);
        Paint tp = new Paint();
        tp.setDither(true);
        tp.setTextSize(getWidth() / getHeight() * 20);

        Random r = new Random(System.currentTimeMillis());
        CharArrayWriter cab = new CharArrayWriter();
        this.answer = "";
        for (int i = 0; i < this.wordLength; i++) {
            char ch = ' ';
            switch (options) {
                case UPPERCASE_ONLY:
                    ch = (char) (r.nextInt(91 - 65) + (65));
                    break;
                case LOWERCASE_ONLY:
                    ch = (char) (r.nextInt(123 - 97) + (97));
                    break;
                case NUMBERS_ONLY:
                    ch = (char) (r.nextInt(58 - 49) + (49));
                    break;
                case LETTERS_ONLY:
                    ch = getLetters(r);
                    break;
                case NUMBERS_AND_LETTERS:
                    ch = getLettersNumbers(r);
                    break;
                default:
                    ch = getLettersNumbers(r);
                    break;
            }
            cab.append(ch);
            this.answer += ch;
        }

        char[] data = cab.toCharArray();
        for (int i = 0; i < data.length; i++) {
            this.x += (30 - (3 * this.wordLength)) + (Math.abs(r.nextInt()) % (65 - (1.2 * this.wordLength)));
            this.y = 50 + Math.abs(r.nextInt()) % 50;
            Canvas cc = new Canvas(bitmap);
            tp.setTextSkewX(r.nextFloat() - r.nextFloat());
            tp.setColor(color());
            cc.drawText(data, i, 1, this.x, this.y, tp);
            tp.setTextSkewX(0);
        }
        return bitmap;
    }

    private char getLetters(Random r) {
        int rint = (r.nextInt(123 - 65) + (65));
        if (((rint > 90) && (rint < 97)))
            getLetters(r);
        else
            mCh = (char) rint;
        return mCh;
    }

    private char getLettersNumbers(Random r) {
        int rint = (r.nextInt(123 - 49) + (49));

        if (((rint > 90) && (rint < 97)))
            getLettersNumbers(r);
        else if (((rint > 57) && (rint < 65)))
            getLettersNumbers(r);
        else
            mCh = (char) rint;
        return mCh;
    }
}
MathCaptcha.java
package info.devexchanges.androidcaptcha;

import java.util.ArrayList;
import java.util.Random;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;

public class MathCaptcha extends Captcha {
 
 protected MathOptions options;
 
 public enum MathOptions{
  PLUS_MINUS,
  PLUS_MINUS_MULTIPLY
 }
 
 public MathCaptcha(int width, int height, MathOptions opt){
  this.height = height;
     setWidth(width);
     this.options = opt;
  usedColors = new ArrayList<Integer>();
  this.image = image();
 }
 
 @Override
 protected Bitmap image() {
     int one = 0;
     int two = 0;
     int math = 0;

     LinearGradient gradient = new LinearGradient(0, 0, getWidth() / 2, this.height / 2, color(), color(), Shader.TileMode.MIRROR);
     Paint p = new Paint();
     p.setDither(true);
     p.setShader(gradient);
     Bitmap bitmap = Bitmap.createBitmap(getWidth(), this.height, Config.ARGB_8888);
     Canvas c = new Canvas(bitmap);
     c.drawRect(0, 0, getWidth(), this.height, p);
     
     LinearGradient fontGrad = new LinearGradient(0, 0, getWidth() / 2, this.height / 2, color(), color(), Shader.TileMode.CLAMP);
     Paint tp = new Paint();
     tp.setDither(true);
     tp.setShader(fontGrad);
     tp.setTextSize(getWidth() / this.height * 20);
     Random r = new Random(System.currentTimeMillis());
  one = r.nextInt(9) + 1;
  two = r.nextInt(9) + 1;
  math = r.nextInt((options == MathOptions.PLUS_MINUS_MULTIPLY)?3:2);
  if (one < two) {
   Integer temp = one;
   one = two;
   two = temp;
  }
  
  switch (math) {
   case 0:
    this.answer = (one + two) + "";
    break;
   case 1:
    this.answer = (one - two) + "";
       break;
   case 2:
    this.answer = (one * two) + "";
       break;
  }
     char[] data = new char[]{String.valueOf(one).toCharArray()[0], oper(math), String.valueOf(two).toCharArray()[0]};
     for (int i=0; i<data.length; i++) {
         x += 30 + (Math.abs(r.nextInt()) % 65);
         y = 50 + Math.abs(r.nextInt()) % 50;
         Canvas cc = new Canvas(bitmap);
         if(i != 1)
          tp.setTextSkewX(r.nextFloat() - r.nextFloat());
         cc.drawText(data, i, 1, x, y, tp);
         tp.setTextSkewX(0);
     }
     return bitmap;
 }
 
 public static char oper(Integer math) {
  switch (math) {
  case 0:
   return '+';
  case 1:
   return '-';
  case 2:
   return '*';
  }
  return '+';
 }
}
    I get these classes from Android-Easy-Captcha project on Github and fix some errors in code!

Usage in Activity/Fragment

    As you can see at the constructors above, we can initialize a new text or math captcha easily by call:
TextCaptcha textCaptcha = new TextCaptcha(600, 150, 4, TextCaptcha.TextOptions.LETTERS_ONLY);
MathCaptcha mathCaptcha = new MathCaptcha(600, 150, MathCaptcha.MathOptions.PLUS_MINUS);
   You will now have a Bitmap with 600x150 pixels. In order to display it to ImageView, only need to call getImage() method (which return a Bitmap object):
imageView.setImageBitmap(textCaptcha.getImage());
imageView1.setImageBitmap(mathCaptcha.getImage());
    The "captcha image" may be look like this:

Check answer from user input

    With checkAnswer() method, we can check whether user input true or false (from EditText) with captcha value:
                //checking text captcha
                if (!textCaptcha.checkAnswer(edtTextCaptcha.getText().toString().trim())) {
                    edtTextCaptcha.setError("Captcha is not match");
                    numberOfCaptchaFalse++;
                } else {
                    Log.d("Main", "captcha is match!");
                }
    Of course, this is the sample output when user input wrong value:
    Important Note: By default, text captcha distinguishes uppercase and lowercase.
    You can make your own output when user input right captcha value, simply showing a Toast like this:

Conclusions

    Now, you've learned the way to create text and math captcha in Android and apply to your own work. Hope this post is helpful with authentication matter in your application. As asual, you can download my sample project by click the button below!

Share


Previous post
« Prev Post
Next post
Next Post »