日常の生活をする上で、様々なものにパスワードが利用されています。

宅配ロッカーや銀行のカード、Webサイトなどあらゆるものにアクセスする際にそれぞれパスワードが求められ、そのパスワードの入力規則も様々です。

そこで、今回の記事では、条件を指定して自動でパスワード用のランダムな文字列を生成する機能の紹介とその実装方法について紹介したいと思います。

0.機能紹介

Androidアプリ「パスワードメモ 画面ロック付きのパスワード管理ツール」にて、搭載されています。

動画での紹介は以下です。

GooglePlayに公開されているアプリは以下です。

 

ここからは、実装方法について書いていきます。

1.Apache Commons Langライブラリを利用する

build.gradleにパッケージの追加をします。


dependencies {
    implementation 'org.apache.commons:commons-lang3:3.7'
}

2.ダイアログ用の画面レイアウトXMLファイルを作成

紹介動画にあるように、文字種別にはグループ化したRadioButton、文字数にはNumberPickerを利用します。

また、横画面にした時に表示が見切れないようにScrollViewで囲っています。

レイアウトファイルの全体は以下になります。


<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:orientation="horizontal">

            <TextView
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:gravity="center"
                android:layout_weight="1"
                android:layout_margin="10dp"
                android:text="@string/character_type" />

            <TextView
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:gravity="center"
                android:layout_weight="1"
                android:layout_margin="10dp"
                android:text="@string/character_count" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:orientation="horizontal">

            <RadioGroup
                android:id="@+id/passwordKindMenu"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:orientation="vertical"
                android:layout_margin="10dp">
                <RadioButton
                    android:id="@+id/radioNumbers"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/numbers_only" />
                <RadioButton
                    android:id="@+id/radioLettersNumbers"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/letters_numbers" />
                <RadioButton
                    android:id="@+id/radioLettersNumbersSymbols"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/letters_numbers_symbols" />
            </RadioGroup>

            <NumberPicker
                android:id="@+id/passwordNumberPicker"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:layout_margin="10dp" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/generatePasswordText"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:gravity="center_vertical"
                android:layout_margin="10dp"
                android:textSize="16dp"
                android:layout_weight="1"
                android:textColor="@android:color/black"
                android:focusable="false" />

            <ImageButton
                android:id="@+id/generateButton"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="10dp"
                android:background="@layout/round_button"
                android:src="@drawable/ic_baseline_autorenew_24" />
        </LinearLayout>
    </LinearLayout>
</ScrollView>

3.ダイアログ表示と操作の実装

最初に、ポイントとなる実装部分について触れて、最後に実装の全容について掲載していきます。

3.1.カスタムレイアウトを利用したダイアログの表示

手順2.で紹介したレイアウトファイルを利用したカスタムダイアログを表示する場合には、AlertDialog.Builderを利用します。


View generatePasswordView = getLayoutInflater().inflate(R.layout.alert_generate_password, null);
final AlertDialog alertDialog = new AlertDialog.Builder(this)
        .setTitle("パスワードを生成")
        .setView(generatePasswordView)
        .setPositiveButton("適用", null)
        .setNegativeButton("破棄", null)
        .create();
alertDialog.show();

3.2.RandomStringUtilsクラスを利用したランダム文字列の生成

Apache Commons LangライブラリのRandomStringUtilsを利用する事で簡単にランダムな文字列を生成する事ができます。

今回文字種別として選択できる3つのパターンについて紹介します。


// 数字のみ
RandomStringUtils.randomNumeric(count);


// 英字+数字
RandomStringUtils.randomAlphanumeric(count);


// 英字+数字+記号
RandomStringUtils.randomGraph(count);

いずれの関数も引数に文字数を取ります。

そして、特に便利なのがrandomGraphです。この関数は、表示可能なASCII文字だけを生成してくれます。つまり、スペースや制御文字などを含まないので、パスワード用のASCII文字列の生成にはちょうどいい感じなんです。

3.3.ダイアログ表示と操作処理の全体の実装

上記2つのポイントを踏まえて、全体のソースコードは以下になります。


public class InputPassword extends AppCompatActivity {

    private int passwordKind;
    private int passwordCount;
    private String generatePassword;
    EditText generatePasswordText;

    @SuppressLint("ResourceType")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_input_password);

        Button generateBtn = findViewById(R.id.generateButton);
        generateBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                generatePasswordDialog();
            }
        });
    }

    // パスワード生成ダイアログ
    private void generatePasswordDialog() {
        View generatePasswordView = getLayoutInflater().inflate(R.layout.alert_generate_password, null);
        // 文字種別ラジオボタンの初期値を設定
        RadioGroup passwordRadio = generatePasswordView.findViewById(R.id.passwordKindMenu);
        passwordRadio.check(R.id.radioLettersNumbers);
        passwordKind = passwordRadio.getCheckedRadioButtonId();
        passwordRadio.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(RadioGroup group, int checkedId) {
                passwordKind = checkedId;
                generatePasswordString();
            }
        });
        // 文字数ピッカーの初期値を設定
        NumberPicker passwordPicker = generatePasswordView.findViewById(R.id.passwordNumberPicker);
        passwordPicker.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        passwordPicker.setMaxValue(32);
        passwordPicker.setMinValue(4);
        passwordPicker.setValue(8);
        passwordCount = passwordPicker.getValue();
        passwordPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() {
            @Override
            public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
                passwordCount = newVal;
                generatePasswordString();
            }
        });
        // ボタンのイベントを設定
        ImageButton generateButton = generatePasswordView.findViewById(R.id.generateButton);
        generateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                generatePasswordString();
            }
        });

        // ダイアログの生成
        final AlertDialog alertDialog = new AlertDialog.Builder(this)
                .setTitle("パスワードを生成")
                .setView(generatePasswordView)
                .setPositiveButton(”適用”, null)
                .setNegativeButton("破棄", null)
                .create();
        alertDialog.show();
        alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // パスワード入力画面のパスワード入力エリアに生成したパスワードを反映させる
                ((EditText)findViewById(R.id.editPassword)).setText(generatePassword);
                alertDialog.dismiss();
            }
        });
        alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                alertDialog.dismiss();
            }
        });

        generatePasswordText = generatePasswordView.findViewById(R.id.generatePasswordText);
        generatePasswordString();
    }

    // パスワード文字列生成
    private void generatePasswordString()
    {
        if (passwordKind == R.id.radioNumbers) {
            generatePassword = RandomStringUtils.randomNumeric(passwordCount);
        } else if (passwordKind == R.id.radioLettersNumbers){
            generatePassword = RandomStringUtils.randomAlphanumeric(passwordCount);
        } else {
            generatePassword = RandomStringUtils.randomGraph(passwordCount);
        }

        // ダイアログ内にあるテキストエリアに生成したパスワードを表示する
        generatePasswordText.setText(generatePassword);
    }
}

実装は以上となります。

RandomStringUtilsには他にも便利なAPIが備わっているのでおすすめです。

ぜひ参考にして頂ければと思います。