본문 바로가기

프로그래밍

[Kotlin] 안드로이드 앱 비밀번호 잠금 구현

반응형

 

카카오톡 앱을 열때 앱을 잠그는 기능이 있습니다.

앱 열고 닫을때 비밀번호를 요구 하는데요 비슷하게 구현 할 수 있습니다.

 

0. 앱 작동시

 

앱 잠금 설정 할때

앱 잠금설정

 

 

 

홈 버튼 누르고 다시 실행시

홈 버튼 누르고 다시실행시

홈버튼 누르고 다시실행하면

앱 잠금설정 되어 있기 때문에 비밀번호를 요구 합니다.

 

 

암호삭제

잠금 비활성화를 누르면 저장되어있던

암호가 삭제 됩니다.

 

 

 

 

1. 레이아웃 

 

1) 비밀번호 입력 액티비티

비밀번호 입력창

 

비밀번호 입력창 레이아웃 소스 activity_app_lock_password.xml 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

   <TextView
       android:id="@+id/etInputInfo"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="비밀번호 입력"/>

   <LinearLayout
        android:id="@+id/ll_passcodes"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <EditText
            android:id="@+id/etPasscode1"
            android:gravity="center"
            android:maxLength="1"
            android:textColor="#000000"
            android:textSize="40sp"
            android:inputType="textPassword|number"
            android:layout_width="0dp"
            android:layout_height="60dp"
            android:layout_margin="4dp"
            android:layout_weight="1"
            android:singleLine="true">
            <requestFocus />
        </EditText>

        <EditText
            android:id="@+id/etPasscode2"
            android:gravity="center"
            android:maxLength="1"
            android:textColor="#000000"
            android:textSize="40sp"
            android:inputType="textPassword|number"
            android:layout_width="0dp"
            android:layout_height="60dp"
            android:layout_margin="4dp"
            android:layout_weight="1"
            android:singleLine="true">
        </EditText>

        <EditText
            android:id="@+id/etPasscode3"
            android:gravity="center"
            android:maxLength="1"
            android:textColor="#000000"
            android:textSize="40sp"
            android:inputType="textPassword|number"
            android:layout_width="0dp"
            android:layout_height="60dp"
            android:layout_margin="4dp"
            android:layout_weight="1"
            android:singleLine="true">
        </EditText>

        <EditText
            android:id="@+id/etPasscode4"
            android:gravity="center"
            android:maxLength="1"
            android:textColor="#000000"
            android:textSize="40sp"
            android:inputType="textPassword|number"
            android:layout_width="0dp"
            android:layout_height="60dp"
            android:layout_margin="4dp"
            android:layout_weight="1"
            android:singleLine="true">
        </EditText>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="bottom"
        android:layout_marginBottom="16dp"
        android:orientation="vertical">
        <TableLayout
            android:id="@+id/tl_keys"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="bottom"
            android:gravity="bottom"
            android:shrinkColumns="*"
            android:stretchColumns="*" >

            <TableRow>

                <Button
                    android:id="@+id/btn1"
                    android:text="1"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>

                <Button
                    android:id="@+id/btn2"
                    android:text="2"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>

                <Button
                    android:id="@+id/btn3"
                    android:text="3"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>
            </TableRow>

            <TableRow>
                <Button
                    android:id="@+id/btn4"
                    android:text="4"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>

                <Button
                    android:id="@+id/btn5"
                    android:text="5"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>

                <Button
                    android:id="@+id/btn6"
                    android:text="6"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>
            </TableRow>

            <TableRow>

                <Button
                    android:id="@+id/btn7"
                    android:text="7"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>

                <Button
                    android:id="@+id/btn8"
                    android:text="8"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>

                <Button
                    android:id="@+id/btn9"
                    android:text="9"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>
            </TableRow>

            <TableRow>

                <Button
                    android:id="@+id/btnClear"
                    android:clickable="false"
                    android:focusable="false"
                    android:focusableInTouchMode="false"
                    android:text="CLEAR" />

                <Button
                    android:id="@+id/btn0"
                    android:text="0"
                    android:textSize="18dp"
                    android:gravity="center"
                    android:layout_margin="5dp"
                    android:layout_width="120dp"
                    android:layout_height="48dp"/>

                <Button
                    android:id="@+id/btnErase"
                    android:text="DEL" />
            </TableRow>
        </TableLayout>
    </LinearLayout>

</LinearLayout>

 

 

 

2) 앱 잠금 테스트 액티비티

 

앱 잠금 테스트 액티비티

비밀번호 잠금 삭제 또는 변경 테스트 할 수 있는 메인 액티비티 레이아웃

activity_app_lock.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

   <Button
       android:id="@+id/btnSetLock"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="잠금 설정"/>

   <Button
       android:id="@+id/btnSetDelLock"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="잠금 비활성화"/>

   <Button
       android:id="@+id/btnChangePwd"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:text="암호 변경"/>

</LinearLayout>

 

 

 

2. 코틀린 소스

 

AppLockConst.kt

MainActivity에서 intent로 AppPassWordActivity로 날려 Type 보낼때 사용 

object AppLockConst {
    val type = "type"
    val ENABLE_PASSLOCK = 1  // 잠금설정
    val DISABLE_PASSLOCK = 2 // 잠금 비활성화
    val CHANGE_PASSWORD = 3 // 암호변경
    val UNLOCK_PASSWORD = 4 // 잠금해제
}

 

 

AppLock.kt

앱 잠금 비밀번호를 SharedPreference에 저장, 삭제, 잠금여부, 비밀번호가 맞는지 확인

class AppLock(context: Context) {

    private var sharedPref = context.getSharedPreferences("appLock", Context.MODE_PRIVATE)

    // 잠금 설정
    fun setPassLock(password : String){
        sharedPref.edit().apply{
            putString("applock", password)
            apply()
        }
    }

    // 잠금 설정 제거
    fun removePassLock(){
        sharedPref.edit().apply{
            remove("applock")
            apply()
        }
    }

    // 입력한 비밀번호가 맞는가?
    fun checkPassLock(password: String): Boolean {
        return sharedPref.getString("applock","0") == password
    }

    // 잠금 설정이 되어있는가?
    fun isPassLockSet(): Boolean {
        if(sharedPref.contains("applock")){
            return true
        }
        return false
    }
}

 

 

 

AppPassWordActivity.kt

비밀번호 입력창 Activity 제어

activity_app_lock_password 레이아웃 제어

class AppPassWordActivity : AppCompatActivity(){
    private var oldPwd =""
    private var changePwdUnlock = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_app_lock_password)

        val buttonArray = arrayListOf<Button>(btn0, btn1, btn2, btn3, btn4, btn5, btn6, btn7 ,btn8, btn9, btnClear, btnErase)
        for (button in buttonArray){
            button.setOnClickListener(btnListener)
        }
    }

	// 버튼 클릭 했을때
    private val btnListener = View.OnClickListener { view ->
            var currentValue = -1
            when(view.id){
                R.id.btn0 -> currentValue = 0
                R.id.btn1 -> currentValue = 1
                R.id.btn2 -> currentValue = 2
                R.id.btn3 -> currentValue = 3
                R.id.btn4 -> currentValue = 4
                R.id.btn5 -> currentValue = 5
                R.id.btn6 -> currentValue = 6
                R.id.btn7 -> currentValue = 7
                R.id.btn8 -> currentValue = 8
                R.id.btn9 -> currentValue = 9
                R.id.btnClear -> onClear()
                R.id.btnErase -> onDeleteKey()
            }

            val strCurrentValue = currentValue.toString() // 현재 입력된 번호 String으로 변경
            if (currentValue != -1){
                when {
                    etPasscode1.isFocused -> {
                        setEditText(etPasscode1, etPasscode2, strCurrentValue)
                    }
                    etPasscode2.isFocused -> {
                        setEditText(etPasscode2, etPasscode3, strCurrentValue)
                    }
                    etPasscode3.isFocused -> {
                        setEditText(etPasscode3, etPasscode4, strCurrentValue)
                    }
                    etPasscode4.isFocused -> {
                        etPasscode4.setText(strCurrentValue)
                    }
                }
            }
            
		// 비밀번호를 4자리 모두 입력시
            if (etPasscode4.text.isNotEmpty() && etPasscode3.text.isNotEmpty() && etPasscode2.text.isNotEmpty() && etPasscode1.text.isNotEmpty()) {
                inputType(intent.getIntExtra("type", 0))
            }
        }

    // 한 칸 지우기를 눌렀을때
    private fun onDeleteKey() {
        when {
            etPasscode1.isFocused -> {
                etPasscode1.setText("")
            }
            etPasscode2.isFocused -> {
                etPasscode1.setText("")
                etPasscode1.requestFocus()
            }
            etPasscode3.isFocused -> {
                etPasscode2.setText("")
                etPasscode2.requestFocus()
            }
            etPasscode4.isFocused -> {
                etPasscode3.setText("")
                etPasscode3.requestFocus()
            }
        }
    }

    // 모두 지우기
    private fun onClear(){
        etPasscode1.setText("")
        etPasscode2.setText("")
        etPasscode3.setText("")
        etPasscode4.setText("")
        etPasscode1.requestFocus()
    }

    // 입력된 비밀번호를 합치기
    private fun inputedPassword():String {
        return "${etPasscode1.text}${etPasscode2.text}${etPasscode3.text}${etPasscode4.text}"
    }

    // EditText 설정
    private fun setEditText(currentEditText : EditText, nextEditText: EditText, strCurrentValue : String){
        currentEditText.setText(strCurrentValue)
        nextEditText.requestFocus()
        nextEditText.setText("")
    }

	// Intent Type 분류
    private fun inputType(type : Int){
        when(type){
            AppLockConst.ENABLE_PASSLOCK ->{ // 잠금설정
                if(oldPwd.isEmpty()){
                    oldPwd = inputedPassword()
                    onClear()
                    etInputInfo.text = "다시 한번 입력"
                }
                else{
                    if(oldPwd == inputedPassword()){
                        AppLock(this).setPassLock(inputedPassword())
                        setResult(Activity.RESULT_OK)
                        finish()
                    }
                    else{
                        onClear()
                        oldPwd = ""
                        etInputInfo.text = "비밀번호 입력"
                    }
                }
            }

            AppLockConst.DISABLE_PASSLOCK ->{ // 잠금삭제
                if(AppLock(this).isPassLockSet()){
                    if(AppLock(this).checkPassLock(inputedPassword())) {
                        AppLock(this).removePassLock()
                        setResult(Activity.RESULT_OK)
                        finish()
                    }
                    else {
                        etInputInfo.text = "비밀번호가 틀립니다."
                        onClear()
                    }
                }
                else{
                    setResult(Activity.RESULT_CANCELED)
                    finish()
                }
            }

            AppLockConst.UNLOCK_PASSWORD ->
                if(AppLock(this).checkPassLock(inputedPassword())) {
                    setResult(Activity.RESULT_OK)
                    finish()
                }else{
                    etInputInfo.text = "비밀번호가 틀립니다."
                    onClear()
                }

            AppLockConst.CHANGE_PASSWORD -> { // 비밀번호 변경
                if (AppLock(this).checkPassLock(inputedPassword()) && !changePwdUnlock) {
                    onClear()
                    changePwdUnlock = true
                    etInputInfo.text = "새로운 비밀번호 입력"
                }
                else if (changePwdUnlock) {
                    if (oldPwd.isEmpty()) {
                        oldPwd = inputedPassword()
                        onClear()
                        etInputInfo.text = "새로운 비밀번호 다시 입력"
                    } else {
                        if (oldPwd == inputedPassword()) {
                            AppLock(this).setPassLock(inputedPassword())
                            setResult(Activity.RESULT_OK)
                            finish()
                        } else {
                            onClear()
                            oldPwd = ""
                            etInputInfo.text = "현재 비밀번호 다시 입력"
                            changePwdUnlock = false
                        }
                    }
                } else {
                    etInputInfo.text = "비밀번호가 틀립니다."
                    changePwdUnlock = false
                    onClear()
                }
            }
        }
    }
}

 

 

MainActivity.kt

잠금 설정, 잠금 비활성화, 암호 변경 버튼을 눌렀을때

Intent를 AppPassWordActivity로 startActivityForResult를 사용하여 보낸다.

class MainActivity : AppCompatActivity() {
    var lock = true // 잠금 상태 여부 확인

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_app_lock)

        init()

		// 잠금 설정 버튼을 눌렀을때
        btnSetLock.setOnClickListener {
            val intent = Intent(this, AppPassWordActivity::class.java).apply {
                putExtra(AppLockConst.type, AppLockConst.ENABLE_PASSLOCK)
            }
            startActivityForResult(intent, AppLockConst.ENABLE_PASSLOCK)
        }

		// 잠금 비활성화 버튼을 눌렀을때
        btnSetDelLock.setOnClickListener{
            val intent = Intent(this, AppPassWordActivity::class.java).apply {
                putExtra(AppLockConst.type, AppLockConst.DISABLE_PASSLOCK)
            }
            startActivityForResult(intent, AppLockConst.DISABLE_PASSLOCK)
        }

		// 암호 변경버튼을 눌렀을때
        btnChangePwd.setOnClickListener {
            val intent = Intent(this, AppPassWordActivity::class.java).apply {
                putExtra(AppLockConst.type, AppLockConst.CHANGE_PASSWORD)
            }
            startActivityForResult(intent, AppLockConst.CHANGE_PASSWORD)
        }
    }

	// startActivityForResult 결과값을 받는다.
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        when(requestCode){
            AppLockConst.ENABLE_PASSLOCK ->
                if(resultCode == Activity.RESULT_OK){
                    Toast.makeText(this, "암호 설정 됨", Toast.LENGTH_SHORT).show()
                    init()
                    lock  = false
                }

            AppLockConst.DISABLE_PASSLOCK ->
                if(resultCode == Activity.RESULT_OK){
                    Toast.makeText(this, "암호 삭제 됨", Toast.LENGTH_SHORT).show()
                    init()
                }

            AppLockConst.CHANGE_PASSWORD ->
                if(resultCode == Activity.RESULT_OK){
                    Toast.makeText(this, "암호 변경 됨", Toast.LENGTH_SHORT).show()
                    lock = false
                }

            AppLockConst.UNLOCK_PASSWORD ->
                if(resultCode == Activity.RESULT_OK){
                    Toast.makeText(this, "잠금 해제 됨", Toast.LENGTH_SHORT).show()
                    lock = false
                }
        }

    }

	// 액티비티가 onStart인 경우
    override fun onStart() {
        super.onStart()
        if(lock && AppLock(this).isPassLockSet()){
            val intent = Intent(this, AppPassWordActivity::class.java).apply {
                putExtra(AppLockConst.type, AppLockConst.UNLOCK_PASSWORD)
            }
            startActivityForResult(intent, AppLockConst.UNLOCK_PASSWORD)
        }

    }

	// 액티비티가 onPause인경우
    override fun onPause() {
        super.onPause()
        if (AppLock(this).isPassLockSet()) {
            lock = true // 잠금로 변경
        }
    }

	// 버튼 비활성화
    private fun init(){
        if (AppLock(this).isPassLockSet()){
            btnSetLock.isEnabled = false
            btnSetDelLock.isEnabled = true
            btnChangePwd.isEnabled = true
            lock = true
        }
        else{
            btnSetLock.isEnabled = true
            btnSetDelLock.isEnabled = false
            btnChangePwd.isEnabled = false
            lock = false
        }
    }
}

 

 

 

3. 앱 작동 순서

1) 메인 액티비티에서 암호 설정 버튼을 누릅니다.

2) 메인 액티비티에서 Intent를 보냅니다.

val intent = Intent(this, AppPassWordActivity::class.java).apply {
	putExtra(AppLockConst.type, AppLockConst.ENABLE_PASSLOCK)
}
startActivityForResult(intent, AppLockConst.ENABLE_PASSLOCK)

 

 

3) AppPassWordActivity 에서 비밀번호 4자리 모두 입력시 inputType() 함수가 실행 됩니다.

  잠금이 설정이 된경우 Activity.RESULT_OK를 결과 값을 메인 액티비티로 전송합니다.

private fun inputType(type : Int){
        when(type){
            AppLockConst.ENABLE_PASSLOCK ->{ // 잠금설정
                if(oldPwd.isEmpty()){
                    oldPwd = inputedPassword()
                    onClear()
                    etInputInfo.text = "다시 한번 입력"
                }
                else{
                    if(oldPwd == inputedPassword()){
                        AppLock(this).setPassLock(inputedPassword())
                        setResult(Activity.RESULT_OK) // 결과 전송
                        finish()
                    }
                    else{
                        onClear()
                        oldPwd = ""
                        etInputInfo.text = "비밀번호 입력"
                    }
                }
            }
            
       ...

 

 

4) 메인 액티비티에서 결과 값을 수신하여 requestCode가 ENABLE_PASSLOCK 인 경우 토스트를 호출 합니다.

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        when(requestCode){
            AppLockConst.ENABLE_PASSLOCK ->
                if(resultCode == Activity.RESULT_OK){
                    Toast.makeText(this, "암호 설정 됨", Toast.LENGTH_SHORT).show()
                    init()
                    lock  = false
                }
 	
        ...

 

반응형