Lock an app using the phone's default lock screen

The snippets below can lock access to the app whenever the user leaves the app and comes back, with an optional timeout.

Uses the Android Lifecycle components to track when the app goes into the background and foreground. Add them to your dependencies:

	// Google Lifecycle Components
    implementation "android.arch.lifecycle:extensions:$lifecycle_version"
    annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version"

And attach a listener to the lifecycle of your Application:

class App : Application() {

	...

	private val userVerificationListener: LockUtil by lazy {
        LockUtil()
    }

    override fun onCreate() {
    	...

        // Set up lifecycle listener
        ProcessLifecycleOwner.get().lifecycle.addObserver(userVerificationListener)
	}

	fun maybeShowScreenUnlock(activity: Activity, requestCode: Int) {
		// R.string.lock_message is "Please verify your identity before using this app",
		// or something similar.
        userVerificationListener.authorize(activity, requestCode, R.string.lock_message)
    }

    fun onAuthorizationEvent(resultCode: Int) : Boolean = userVerificationListener.onAuthorizationEvent(resultCode)
}

Note that for good measure you should inject the LockUtil into your activity instead of referencing the App class from your Activities.

In your base Activity class, or in the Activities that should be locked, override onResume and onActivityResult:


    companion object {
        const val REQUEST_CODE : Int = 123
    }

	override fun onResume() {
		(application as App).maybeShowScreenUnlock(this, REQUEST_CODE)
	}

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_CODE) {
            val ok = (application as App).onAuthorizationEvent(resultCode)
            if (!ok) {
                // Close the entire stack
                finishAffinity()
                // Show a message to the user.
                // Though a Snackbar is usually better than a Toast,
                // a Toast can linger while the activity is closed,
                // which is excellent in this case.
                Toast.makeText(this, R.string.warning_app_locked, Toast.LENGTH_LONG).show()
            }
        }
    }

LockUtil.kt

And finally, the content of the utility class:

package com.pixplicity.example.util

import android.app.Activity
import android.app.KeyguardManager
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.OnLifecycleEvent
import android.content.Context
import android.support.annotation.StringRes
import android.util.Log
import com.pixplicity.example.BuildConfig
import com.pixplicity.example.R

/**
 * Helps with the screen unlock feature (pattern/pin/fingerprint/etc).
 *
 * Call {@link #authorize()} to initiate the unlock. Will skip
 * and return true when already authorized, or when not set up
 * or not supported.
 */
class LockUtil : LifecycleObserver {

    companion object {

        /**
         * Re-lock after 3 minutes
         */
        private val TEMP_AUTH_TIMEOUT_MILLIS: Long = if (BuildConfig.DEBUG) 1000 else 3 * 60 * 1000

        fun isSupported(): Boolean = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP

    }

    /**
     * Keeps track of the last time the user was verified
     */
    private var lastExitMillis: Long = 0

    /**
     * Should be true while the user is verified and still
     * using the app
     */
    private var isTempAuthorized: Boolean = false

    /**
     * Resets the authorization. Call this e.g. when the main screen
     * is exited by the user.
     */
    fun unauthorize() {
        lastExitMillis = 0
        isTempAuthorized = false
    }

    /**
     * Checks if the user is still authorized, so we don't ask again too often
     */
    private fun isTempAuthorized(): Boolean =
            isTempAuthorized || (System.currentTimeMillis() - lastExitMillis) < TEMP_AUTH_TIMEOUT_MILLIS

    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    fun onAppMovedToForeground() {
        Log.v("LockUtil", "App is back to foreground, last seen $lastExitMillis")
        if (isTempAuthorized()) {
            isTempAuthorized = true
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun onAppMovedToBackground() {
        lastExitMillis = if (isTempAuthorized) System.currentTimeMillis() else 0
        isTempAuthorized = false
        Log.v("LockUtil", "App moved to background, timestamp $lastExitMillis")
    }

    /**
     * Starts the verification flow if needed
     */
    fun authorize(activity: Activity, requestCode: Int, @StringRes message: Int): Boolean {
        if (isSupported() && !isTempAuthorized()) {
            val km = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager?
            if (km != null) {
                val intent = km.createConfirmDeviceCredentialIntent(
                        activity.getString(R.string.app_name),
                        activity.getString(message))
                if (intent != null) {
                    activity.startActivityForResult(intent, requestCode)
                    return false
                }
            }
        }
        return true
    }

    fun onAuthorizationEvent(resultCode: Int) : Boolean {
        isTempAuthorized = (resultCode == Activity.RESULT_OK)
        return isTempAuthorized
    }
}