How to detect user inactivity in Android

AndroidUser Inactivity

Android Problem Overview


User start my app and logs in.
Selects Session Timeout to be 5 mins.
Does some operations on the app. (all in foreground)
Now User bring Myapp to background and starts some other app.
----> Count down timer starts and logs out user after 5 mins
OR user turns the screen OFF.
----> Count down timer starts and logs out user after 5 mins

I want the same behavior even when the app is in the foreground but user doesn't interact with the app for a long-time say 6-7 mins. Assume the screen is ON all the time. I want to detect kind of user inactivity (No interaction with app even though the app is in the foreground) and kick start my count down timer.

Android Solutions


Solution 1 - Android

I came up with a solution that I find quite simple based on Fredrik Wallenius's answer. This a base activity class that needs to be extended by all activities.

public class MyBaseActivity extends Activity {
    
    public static final long DISCONNECT_TIMEOUT = 300000; // 5 min = 5 * 60 * 1000 ms
  

    private static Handler disconnectHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // todo
            return true;
        }
    });

    private static Runnable disconnectCallback = new Runnable() {
        @Override
        public void run() {
            // Perform any required operation on disconnect
        }
    };
    
    public void resetDisconnectTimer(){
        disconnectHandler.removeCallbacks(disconnectCallback);
        disconnectHandler.postDelayed(disconnectCallback, DISCONNECT_TIMEOUT);
    }

    public void stopDisconnectTimer(){
        disconnectHandler.removeCallbacks(disconnectCallback);
    }

    @Override
    public void onUserInteraction(){
        resetDisconnectTimer();
    }

    @Override
    public void onResume() {
        super.onResume();
        resetDisconnectTimer();
    }

    @Override
    public void onStop() {
        super.onStop();
        stopDisconnectTimer();
    }
}

Solution 2 - Android

I don't know a way of tracking inactivity but there is a way to track user activity. You can catch a callback called onUserInteraction() in your activities that is called every time the user does any interaction with the application. I'd suggest doing something like this:

@Override
public void onUserInteraction(){
	MyTimerClass.getInstance().resetTimer();
}

If your app contains several activities, why not put this method in an abstract super class (extending Activity) and then have all you activities extending it.

Solution 3 - Android

I think you should go with this code, this is for 5min idle session timeout:->

Handler handler;
Runnable r;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    handler = new Handler();
    r = new Runnable() {

       @Override
       public void run() {
            // TODO Auto-generated method stub
            Toast.makeText(MainActivity.this, "user is inactive from last 5 minutes",Toast.LENGTH_SHORT).show();
        }
    };
    startHandler();
}
@Override
public void onUserInteraction() {
     // TODO Auto-generated method stub
     super.onUserInteraction();
     stopHandler();//stop first and then start
     startHandler();
}
public void stopHandler() {
    handler.removeCallbacks(r);
}
public void startHandler() {
    handler.postDelayed(r, 5*60*1000); //for 5 minutes 
}

Solution 4 - Android

public class MyApplication extends Application {
      private int lastInteractionTime;
      private Boolean isScreenOff = false; 
      public void onCreate() {
        super.onCreate();
        // ......   
        startUserInactivityDetectThread(); // start the thread to detect inactivity
        new ScreenReceiver();  // creating receive SCREEN_OFF and SCREEN_ON broadcast msgs from the device.
      }
     
      public void startUserInactivityDetectThread() {
        new Thread(new Runnable() {
          @Override
          public void run() {
            while(true) {
    		  Thread.sleep(15000); // checks every 15sec for inactivity
              if(isScreenOff || getLastInteractionTime()> 120000 ||  !isInForeGrnd)
                {
                  //...... means USER has been INACTIVE over a period of
                  // and you do your stuff like log the user out 
                }
              }
          }
        }).start();
      }
    
      public long getLastInteractionTime() {
        return lastInteractionTime;
      }
    
      public void setLastInteractionTime(int lastInteractionTime) {
    	this.lastInteractionTime = lastInteractionTime;
      }
    
      private class ScreenReceiver extends BroadcastReceiver {
    	
        protected ScreenReceiver() {
           // register receiver that handles screen on and screen off logic
           IntentFilter filter = new IntentFilter();
           filter.addAction(Intent.ACTION_SCREEN_ON);
           filter.addAction(Intent.ACTION_SCREEN_OFF);
           registerReceiver(this, filter);
    	}
    
    	@Override
    	public void onReceive(Context context, Intent intent) {
    	  if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
    	    isScreenOff = true;
    	  } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
    	    isScreenOff = false;
    	  }
        }
      }
    }
 

isInForeGrnd ===> logic is not shown here as it is out of scope of the question

You can wake the lock to the cpu by using the device code below-

  if(isScreenOff || getLastInteractionTime()> 120000 ||  !isInForeGrnd)
    {
      //...... means USER has been INACTIVE over a period of
      // and you do your stuff like log the user out 
    
      PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    
      boolean isScreenOn = pm.isScreenOn();
      Log.e("screen on.................................", "" + isScreenOn);
    
      if (isScreenOn == false) {
    
        PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "MyLock");
    
        wl.acquire(10000);
        PowerManager.WakeLock wl_cpu = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyCpuLock");
    
        wl_cpu.acquire(10000);
      }
    }

Solution 5 - Android

@Override
public void onUserInteraction() {
    super.onUserInteraction();
    delayedIdle(IDLE_DELAY_MINUTES);
}
    
Handler _idleHandler = new Handler();
Runnable _idleRunnable = new Runnable() {
    @Override
    public void run() {
        //handle your IDLE state
    }
};

private void delayedIdle(int delayMinutes) {
    _idleHandler.removeCallbacks(_idleRunnable);
    _idleHandler.postDelayed(_idleRunnable, (delayMinutes * 1000 * 60));
}

Solution 6 - Android

There is no concept of "user inactivity" at the OS level, beyond the ACTION_SCREEN_OFF and ACTION_USER_PRESENT broadcasts. You will have to define "inactivity" somehow within your own application.

Solution 7 - Android

User inactivity can detect using onUserInteraction() override method in android

  @Override
    public void onUserInteraction() {
        super.onUserInteraction();
       
    }

Here is the sample code, signout (HomeActivity-->LoginActivity) after 3min when user inactive

public class HomeActivity extends AppCompatActivity {

    private static String TAG = "HomeActivity";
    private Handler handler;
    private Runnable r;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);


        handler = new Handler();
        r = new Runnable() {

            @Override
            public void run() {

                Intent intent = new Intent(getApplicationContext(), LoginActivity.class);
                startActivity(intent);
                Log.d(TAG, "Logged out after 3 minutes on inactivity.");
                finish();

                Toast.makeText(HomeActivity.this, "Logged out after 3 minutes on inactivity.", Toast.LENGTH_SHORT).show();
            }
        };

        startHandler();

    }

    public void stopHandler() {
        handler.removeCallbacks(r);
        Log.d("HandlerRun", "stopHandlerMain");
    }

    public void startHandler() {
        handler.postDelayed(r, 3 * 60 * 1000);
        Log.d("HandlerRun", "startHandlerMain");
    }

    @Override
    public void onUserInteraction() {
        super.onUserInteraction();
        stopHandler();
        startHandler();
    }

    @Override
    protected void onPause() {

        stopHandler();
        Log.d("onPause", "onPauseActivity change");
        super.onPause();

    }

    @Override
    protected void onResume() {
        super.onResume();
        startHandler();

        Log.d("onResume", "onResume_restartActivity");

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopHandler();
        Log.d("onDestroy", "onDestroyActivity change");

    }

}

Solution 8 - Android

Handling user in interaction timeout in KOTLIN:

     //Declare handler
      private var timeoutHandler: Handler? = null
      private var interactionTimeoutRunnable: Runnable? = null

 override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_aspect_ratio)
    
       //Initialise handler
      timeoutHandler =  Handler();
      interactionTimeoutRunnable =  Runnable {
         // Handle Timeout stuffs here
          }

      //start countdown
      startHandler()
}

// reset handler on user interaction
override fun onUserInteraction() {
      super.onUserInteraction()
      resetHandler()
}

 //restart countdown
fun resetHandler() {
      timeoutHandler?.removeCallbacks(interactionTimeoutRunnable);
      timeoutHandler?.postDelayed(interactionTimeoutRunnable, 10*1000); //for 10 second

}

 // start countdown
fun startHandler() {
    timeoutHandler?.postDelayed(interactionTimeoutRunnable, 10*1000); //for 10 second
}

Solution 9 - Android

In my activity base class I created protected class:

protected class IdleTimer
{
	private Boolean isTimerRunning;
	private IIdleCallback idleCallback;
	private int maxIdleTime;
	private Timer timer;
	
	public IdleTimer(int maxInactivityTime, IIdleCallback callback)
	{
		maxIdleTime = maxInactivityTime;
		idleCallback = callback;
	}
	
	/*
	 * creates new timer with idleTimer params and schedules a task
	 */
	public void startIdleTimer()
	{
		timer = new Timer();			
		timer.schedule(new TimerTask() {
		
			@Override
			public void run() {				
				idleCallback.inactivityDetected();
			}
		}, maxIdleTime);
		isTimerRunning = true;
	}
	
	/*
	 * schedules new idle timer, call this to reset timer
	 */
	public void restartIdleTimer()
	{
		stopIdleTimer();
		startIdleTimer();
	}
	
	/*
	 * stops idle timer, canceling all scheduled tasks in it
	 */
	public void stopIdleTimer()
	{
		timer.cancel();
		isTimerRunning = false;
	}
	
	/*
	 * check current state of timer
	 * @return boolean isTimerRunning
	 */
	public boolean checkIsTimerRunning()
	{
		return isTimerRunning;
	}
}

protected interface IIdleCallback
{
	public void inactivityDetected();
}

So in onResume method - you can specify action in your callback what do you wish to do with it...

idleTimer = new IdleTimer(60000, new IIdleCallback() {
			@Override
			public void inactivityDetected() {
				...your move...
			}
		});
		idleTimer.startIdleTimer();

Solution 10 - Android

During my Search I found a lot of answers but this is the best answer I got. But limitation of this code is that it works only for activity not for whole application. Take this as a reference.

myHandler = new Handler();
myRunnable = new Runnable() {
    @Override
    public void run() {
        //task to do if user is inactive

    }
};
@Override
public void onUserInteraction() {
    super.onUserInteraction();
    myHandler.removeCallbacks(myRunnable);
    myHandler.postDelayed(myRunnable, /*time in milliseconds for user inactivity*/);
}

for e.g you used 8000, the task will be done after 8 seconds of user inactivity.

Solution 11 - Android

Here is a complete solution that Handles user inactivity after few mins (e.g. 3mins). This solves the common issues such as Activity jumping into the foreground when the App is in the background upon time out.

Firstly, we create a BaseActivity which all other Activity can extend.

This is the BaseActivity code.

package com.example.timeout;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.view.Window;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;


import javax.annotation.Nullable;

public class BaseActivity extends AppCompatActivity implements LogoutListener {

    private Boolean isUserTimedOut = false;
    private static Dialog mDialog;



    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ((TimeOutApp) getApplication()).registerSessionListener(this);
        ((TimeOutApp) getApplication()).startUserSession();

    }

    @Override
    public void onUserInteraction() {
        super.onUserInteraction();

        
    }

    @Override
    protected void onResume() {
        super.onResume();

        if (isUserTimedOut) {
            //show TimerOut dialog
            showTimedOutWindow("Time Out!", this);

        } else {

            ((TimeOutApp) getApplication()).onUserInteracted();

        }

    }

    @Override
    public void onSessionLogout() {


        isUserTimedOut = true;

    }


    public void showTimedOutWindow(String message, Context context) {


        if (mDialog != null) {
            mDialog.dismiss();
        }
        mDialog = new Dialog(context);


        mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
        mDialog.setContentView(R.layout.dialog_window);

        mDialog.setCancelable(false);
        mDialog.setCanceledOnTouchOutside(false);

        TextView mOkButton = (TextView) mDialog.findViewById(R.id.text_ok);
        TextView text_msg = (TextView) mDialog.findViewById(R.id.text_msg);

        if (message != null && (!TextUtils.isEmpty(message)) && (!message.equalsIgnoreCase("null"))) {
            text_msg.setText(message);

        }


        mOkButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (mDialog != null){

                    mDialog.dismiss();

                    Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                    startActivity(intent);
                 
                    finish();
                }


            }
        });

        if(!((Activity) context).isFinishing())
        {
            //show dialog
            mDialog.show();
        }

    }

}

Next, we create an interface for our "Logout Listener"

package com.example.timeout;

public interface LogoutListener {

    void onSessionLogout();

}

Finally, we create a Java class which extend "Application"

package com.example.timeout;

import android.app.Application;

import java.util.Timer;
import java.util.TimerTask;

public class TimeOutApp extends Application {

    private LogoutListener listener;
    private Timer timer;
	private static final long INACTIVE_TIMEOUT = 180000; // 3 min


    public void startUserSession () {
        cancelTimer ();

        timer = new Timer ();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {

                listener.onSessionLogout ();

            }
        }, INACTIVE_TIMEOUT);

    }

    private void cancelTimer () {
        if (timer !=null) timer.cancel();
    }

    public void registerSessionListener(LogoutListener listener){
        this.listener = listener;
    }

    public void onUserInteracted () {
        startUserSession();
    }


}

Note: Don't forget to add the "TimeOutApp" class to your application tag inside your manifest file

<application
        android:name=".TimeOutApp">
		</application>

Solution 12 - Android

I think it needs to be by combining the timer with the last activty time.

So like this:

  1. In onCreate(Bundle savedInstanceState) start a timer, say 5 minutes

  2. In onUserInteraction() just store the current time

Pretty simple so far.

Now when the timer pop do like this:

  1. Take the current time and subtract the stored interaction time to get timeDelta
  2. If timeDelta is >= the 5 minutes, you are done
  3. If timeDelta is < the 5 minutes start the timer again, but this time use 5 minutes - the stored time. In other words, 5 minute form the last interaction

Solution 13 - Android

I had similar situation to the SO question, where i needed to track the user inactivity for 1 minute then redirect the user to start Activity, i needed also to clear the activity stack.

Based on @gfrigon answer i come up with this solution.

ActionBar.java
public abstract class ActionBar extends AppCompatActivity {

    public static final long DISCONNECT_TIMEOUT = 60000; // 1 min

    private final MyHandler mDisconnectHandler = new MyHandler(this);

    private Context mContext;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mContext = this;
    }



    /*
    |--------------------------------------------------------------------------
    | Detect user inactivity in Android
    |--------------------------------------------------------------------------
    */

    // Static inner class doesn't hold an implicit reference to the outer class

    private static class MyHandler extends Handler {

        // Using a weak reference means you won't prevent garbage collection

        private final WeakReference<ActionBar> myClassWeakReference;

        public MyHandler(ActionBar actionBarInstance) {

            myClassWeakReference = new WeakReference<ActionBar>(actionBarInstance);
        }

        @Override
        public void handleMessage(Message msg) {

            ActionBar actionBar = myClassWeakReference.get();

            if (actionBar != null) {
                // ...do work here...
            }
        }
    }


    private Runnable disconnectCallback = new Runnable() {

        @Override
        public void run() {

            // Perform any required operation on disconnect

            Intent startActivity = new Intent(mContext, StartActivity.class);
            
            // Clear activity stack

            startActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

            startActivity(startActivity);
        }
    };

    public void resetDisconnectTimer() {

        mDisconnectHandler.removeCallbacks(disconnectCallback);
        mDisconnectHandler.postDelayed(disconnectCallback, DISCONNECT_TIMEOUT);
    }

    public void stopDisconnectTimer() {

        mDisconnectHandler.removeCallbacks(disconnectCallback);
    }

    @Override
    public void onUserInteraction(){

        resetDisconnectTimer();
    }

    @Override
    public void onResume() {

        super.onResume();
        resetDisconnectTimer();
    }

    @Override
    public void onStop() {

        super.onStop();
        stopDisconnectTimer();
    }
}
Complementary resources

Android: Clear Activity Stack

This Handler class should be static or leaks might occur

Solution 14 - Android

Best thing is to handle this across your whole app (assuming you have multiple activities) by registering AppLifecycleCallbacks in the Application calss. You can use registerActivityLifecycleCallbacks() in the Application class with the following callbacks (I recommend creating an AppLifecycleCallbacks class that extends the ActivityLifecycleCallbacks):

public interface ActivityLifecycleCallbacks {
    void onActivityCreated(Activity activity, Bundle savedInstanceState);
    void onActivityStarted(Activity activity);
    void onActivityResumed(Activity activity);
    void onActivityPaused(Activity activity);
    void onActivityStopped(Activity activity);
    void onActivitySaveInstanceState(Activity activity, Bundle outState);
    void onActivityDestroyed(Activity activity);
}

Solution 15 - Android

open class SubActivity : AppCompatActivity() {
    var myRunnable:Runnable
    private var myHandler = Handler()

    init {
        myRunnable = Runnable{
            toast("time out")
            var intent = Intent(this, MainActivity::class.java)
            startActivity(intent)

        }
    }

    fun toast(text: String) {
        runOnUiThread {
            val toast = Toast.makeText(applicationContext, text, Toast.LENGTH_SHORT)
            toast.show()
        }
    }

   override fun onUserInteraction() {
        super.onUserInteraction();
        myHandler.removeCallbacks(myRunnable)
        myHandler.postDelayed(myRunnable, 3000)
    }

    override fun onPause() {
        super.onPause()
        myHandler.removeCallbacks(myRunnable)
    }

    override fun onResume() {
            super.onResume()
            myHandler.postDelayed(myRunnable, 3000)
    }
}

Extend your Activity with

YourActivity:SubActivity(){}

to get to MainActivity when User is inactive after 3000 millisec on YourActivity

I used an previous answer and converted it to kotlin.

Solution 16 - Android

The real way

You can use this technique to detect how long the user was inactive (even when app is in background).

  1. Create a SharedPreference & its Editor object. Then declare 3 long varibale such as :
mMillisUntilFinished = pref.getLong("millisUntilFinished",60*1000); // Replace with your time
long userExitedMillis = pref.getLong("userExitedMillis",0);
long timeLeft = mMillisUntilFinished - (System.currentTimeMillis() - userExitedMillis);
  1. Pass timeLeft as millisInFuture. Inside timer, assign millisUntilFinished to a public variable in every tick
new CountDownTimer(timeLeft,1000){
            @Override
            public void onTick(long millisUntilFinished) {
                Log.d("TAG", "Time left : " + millisUntilFinished/1000 + " sec");
                mMillisUntilFinished = millisUntilFinished;
            }

            @Override
            public void onFinish() {
                // Timer completed
            }
        }.start();

  1. Save this mMillisUntilFinished variable & current time in shared preference at onStop().
@Override
    protected void onStop() {
        super.onStop();
        editor.putLong("millisUntilFinished",mMillisUntilFinished);
        editor.putLong("userExitedMillis",System.currentTimeMillis());
        editor.apply();
    }

Explanation

If you subtract userExitedMillis (the time when user exited) from System.currentTimeMillis() (the time when user starts the activity) then you will get the activity inactive time (in milli second). Just subtract this inactive time from timeLeft

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAkhView Question on Stackoverflow
Solution 1 - AndroidgfrigonView Answer on Stackoverflow
Solution 2 - AndroidFredrik WalleniusView Answer on Stackoverflow
Solution 3 - AndroidPradeep GuptaView Answer on Stackoverflow
Solution 4 - AndroidAkhView Answer on Stackoverflow
Solution 5 - AndroidsoenkeView Answer on Stackoverflow
Solution 6 - AndroidCommonsWareView Answer on Stackoverflow
Solution 7 - Androiddamith alahakoonView Answer on Stackoverflow
Solution 8 - AndroidHitesh SahuView Answer on Stackoverflow
Solution 9 - Androiddivide by zeroView Answer on Stackoverflow
Solution 10 - AndroidA_rmasView Answer on Stackoverflow
Solution 11 - AndroidOyewo RemiView Answer on Stackoverflow
Solution 12 - AndroidRobert WiebeView Answer on Stackoverflow
Solution 13 - AndroidchebabyView Answer on Stackoverflow
Solution 14 - AndroidboxView Answer on Stackoverflow
Solution 15 - AndroidTom PeakView Answer on Stackoverflow
Solution 16 - AndroidʀᴀʜɪʟView Answer on Stackoverflow