Android: How to set passcode/PIN for my app only?
Asked Answered
B

1

6

I am working on an app where I need to set security feature by setting passcode/password/pin for the user so that whenever they open the app from the background, the app asks for password/passcode/pin. I read a few articles/solutions like this but they didn't help me. The same functionality we found in normal banking apps in which whenever you open an app, they will ask for passcode/fingerprint.

I have already set up the logic to save the passcode/pin in shared preference but I am not sure about when to ask it. I know that we can't replace splash screen with passcode/pin activity, because sometimes from app's homeScreen/MainActivity, user press home button and when they again open the app from recent apps, the app should ask for passcode/pin to resume the app use.

Any help would be appreciated.

Bucella answered 19/11, 2020 at 7:36 Comment(5)
Do you want the pin to be the same as the devices or user set?Provinciality
@Provinciality user set pin. I have mentioned that I am storing locally the passcode/pin in shared preference.Bucella
Check this out #10107337Provinciality
You implemented your own pin activity and logic to save/check the input pin. All you need right now is how to display it in the correct way, isn't it?Netti
@SonTruong exactly.Bucella
N
3

This is an interesting question, I will share my thought on this question and give a solution as well.

Terminology:

App Lock Type: A generic name for pin/pincode/password/passcode, etc. (in the following section, I will use the pin name to demonstrate)

PinActivity: A screen where users input their pin to verify themself

Story:

For apps that require users to input pin, they usually want to make sure sensitive information is not leaked/stolen by other people. So we will categorize app activities into 2 groups.

  • Normal activities: Doesn't contain any sensitive information, usually before users logged in to the app, such as SplashActivity, LoginActivity, RegistrationActivity, PinActivity, etc.

  • Secured activities: Contain sensitive information, usually after users logged in, such as MainActivity, HomeActivity, UserInfoActivity, etc.

Conditions:

For secured activities, we must make sure users always input their pin before viewing the content by showing the PinActivity. This activity will be shown in the following scenarios:

  • [1] When users open a secured activity form a normal activity, such as from SplashActivity to MainActivity

  • [2] When users open a secured activity by tapping on Notifications, such as they tap on a notification to open MainActivity

  • [3] When users tap on the app from the Recents screen

  • [4] When the app starts a secured activity from another place like Services, Broadcast Receiver, etc.

Implementation:

For case [1] [2] and [4], before start a secured activity we will add an extra to the original intent. I will create a file named IntentUtils.kt

IntentUtils.kt

const val EXTRA_IS_PIN_REQUIRED = "EXTRA_IS_PIN_REQUIRED"

fun Intent.secured(): Intent {
    return this.apply {
        putExtra(EXTRA_IS_PIN_REQUIRED, true)
    }
}

Use this class from normal activities, notifications, services, etc.

startActivity(Intent(this, MainActivity::class.java).secured())

For case [3], I will use 2 APIs:

First I create a base activity, all normal activitis must extend from this class

BaseActivity.kt

open class BaseActivity : AppCompatActivity() {
    
    // This method indicates that a pin is required if 
    // users want to see the content inside.
    open fun isPinRequired() = false
}

Second I create a secured activity, all secured activities must extend from this class

SecuredActivity.kt

open class SecuredActivity : BaseActivity() {
    override fun isPinRequired() = true

    // This is useful when launch a secured activity with 
    // singleTop, singleTask, singleInstance launch mode
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        setIntent(intent)
    }
}

Third I create a class that extends from the Application, all logic are inside this class

MyApplication.kt

class MyApplication : Application() {

    private var wasEnterBackground = false

    override fun onCreate() {
        super.onCreate()
        registerActivityLifecycleCallbacks(ActivityLifecycleCallbacksImpl())
        ProcessLifecycleOwner.get().lifecycle.addObserver(LifecycleObserverImpl())
    }

    private fun showPinActivity() {
        startActivity(Intent(this, PinActivity::class.java))
    }

    inner class LifecycleObserverImpl : LifecycleObserver {

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun onEnterBackground() {
            wasEnterBackground = true
        }
    }

    inner class ActivityLifecycleCallbacksImpl : ActivityLifecycleCallbacks {

        override fun onActivityResumed(activity: Activity) {
            val baseActivity = activity as BaseActivity
            if (!wasEnterBackground) {
                // Handle case [1] [2] and [4]
                val removed = removeIsPinRequiredKeyFromActivity(activity)
                if (removed) {
                    showPinActivity()
                }
            } else {
                // Handle case [3]
                wasEnterBackground = false
                if (baseActivity.isPinRequired()) {
                    removeIsPinRequiredKeyFromActivity(activity)
                    showPinActivity()
                }
            }
        }

        private fun removeIsPinRequiredKeyFromActivity(activity: Activity): Boolean {
            val key = EXTRA_IS_PIN_REQUIRED
            if (activity.intent.hasExtra(key)) {
                activity.intent.removeExtra(key)
                return true
            }
            return false
        }

        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
        override fun onActivityStarted(activity: Activity) {}
        override fun onActivityPaused(activity: Activity) {}
        override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
        override fun onActivityStopped(activity: Activity) {}
        override fun onActivityDestroyed(activity: Activity) {}
    }
}

Conclusion:

This solution works for those cases that I mentioned before, but I haven't tested the following scenarios:

  • When start a secured activity has launch mode singleTop|singleTask|singleInstance
  • When application killed by the system on low memory
  • Other scenarios that someone might encounter (if yes please let me know in the comments section).
Netti answered 20/11, 2020 at 4:59 Comment(3)
Cảm ơn chia sẻ từ anh,liệu có thể kết hợp hết 1 2 3 4 không ạ, xin lỗi cho câu hỏi em mới bắt đầu.Tubby
@Tubby em thông cảm vì giờ anh mới trả lời được, anh cũng chưa có thời gian để kết hợp nữa. Em mới bắt đầu có thể tìm hiểu và dựa vào một số gợi ý từ câu trả lời của anh để áp dụng vào ứng dụng em đang làm. Nếu có gì khó khăn anh có thể giúp thì anh sẵn sàng.Netti
Cảm ơn anh. Em mới tìm hiểu về android và mục đích của em là xây dựng một ứng dụng đơn giản theo yêu cầu của mình. Do thời gian hạn chế nên dường như em chỉ cố gắng lắp ráp mọi thứ mà không có kiến thức nhiều :v. Hiện em có câu hỏi nhỏ nhưng chưa biết cách giải quyết ở đây, có lẽ em diễn tả hơi khó hiểu mong anh thông cảm. Cảm ơn anh rất nhiều.Tubby

© 2022 - 2024 — McMap. All rights reserved.