Android How to Implement Timeout Lockscreen

August 20, 2018
Return to Main Activity if application idle for N minutes

I am building a security sensitive app, and I need the app to return to login screen if the app is idle for N minutes.

First, I need to implement a global activity watcher for all activities.

  • Timer start when a activity is started (and timer stop when activity is stopped)
  • Later we will implemenent a mechanism to check for user interaction (touch event).
  • When activeCount > 0, it means the application is still in foreground/active, thus launch the LoginActivity when timeout. Make sure to clear activity task/stack when launching LoginActivity to prevent ability to go back.
  • When activeCount <= 0, it means the application is send to background, thus we just finish/exit. Launching an activity when application in background will cause the activity to force appear and interrupt the user’s current app.
  • Skip this process if current activity is LoginActivity.
class ActivityWatcher : Application.ActivityLifecycleCallbacks {

    companion object {
        private const val TAG = "ActivityWatcher"
    }

    private var activeCount: Int = 0

    private var lockTimer: Timer = Timer()
    private var sleep: Long = 5000

    fun initSleep() {

    }

    fun startLockTimer(activity: Activity?) {
        // val sharedPref = PreferenceManager.getDefaultSharedPreferences(activity?.applicationContext)
        // var sleep = sharedPref.getString(activity?.getString(R.string.pref_key_auto_lock_duration), "0").toLong() * 1000
        
        Log.d(TAG, "startLockTimer=$sleep")

        if (sleep > 0) {
            lockTimer.cancel()
            lockTimer = Timer()
            lockTimer.schedule(sleep) {
                Log.d(TAG, "Timer triggered")
                activity?.apply {
                    // just to be safe
                    try {
                        if (activeCount > 0) {
                            finish()
                            val intent = Intent(this, LoginActivity::class.java)
                            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                            startActivity(intent)
                        } else {
                            // moveTaskToBack(true)
                            finishAffinity()
                        }
                    }
                    catch (e: Exception) {
                        e.printStackTrace()
                        Crashlytics.logException(e)
                    }
                }
            }
        }
    }

    private fun stopLockTimer() {
        Log.d(TAG, "stopLockTimer")
        lockTimer.cancel()
        lockTimer = Timer()
    }


    override fun onActivityCreated(activity: Activity?, bundle: Bundle?) {

    }

    override fun onActivityStarted(activity: Activity?) {
        activeCount++
        Log.d(TAG, "onActivityStarted=$activeCount")

        Log.d(TAG, activity?.javaClass?.name)
        if (activity !is LoginActivity) {
            startLockTimer(activity)
        }
        else {
            // just in case
            stopLockTimer()
        }
    }

    override fun onActivityPaused(activity: Activity) {

    }

    override fun onActivityResumed(activity: Activity?) {

    }

    override fun onActivityStopped(activity: Activity?) {
        activeCount--
        Log.d(TAG, "onActivityStopped=$activeCount")
        if (activeCount > 0 && activity !is LoginActivity) {
            // when switch activity, will trigger start then stop (end up not starting any)
            // stopLockTimer()
        }
    }

    override fun onActivityDestroyed(activity: Activity?) {

    }

    override fun onActivitySaveInstanceState(activity: Activity?, p1: Bundle?) {

    }
}

Extend Application and call registerActivityLifecycleCallbacks.

class LuaApp : MultiDexApplication() {
    override fun onCreate() {
        super.onCreate()

        val watcher = ActivityWatcher()
        // store in global singleton
        App.activityWatcher = watcher
        registerActivityLifecycleCallbacks(watcher)        
    }
}

NOTE: Refer to Android Register/Extend Application Class.

Create App singleton class to hold ActivityWatcher reference.

object App {
    lateinit var activityWatcher: ActivityWatcher
}

Create a base activity class to listen to onUserInteraction.

  • Whenever user interaction happens (not idle), the timer is restarted.
open class LuaActivity: AppCompatActivity() {
    companion object {
        private const val TAG : String = "LuaActivity"
    }

    override fun onUserInteraction() {
        Log.d(TAG, "onUserInteraction")
        super.onUserInteraction()
        if (activity !is LoginActivity) { // no
            App.activityWatcher.startLockTimer(this)
        }
    }    
}

Make sure every activity extend LuaActivity instead of the usual AppCompatActivity.

class MainActivity : LuaActivity() {

}

NOTE: Refer to Kotlin Singleton.

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.