How to detect user inactivity in Android
Asked Answered
D

18

120

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.

Disarm answered 17/11, 2010 at 20:13 Comment(1)
Could you always have that timer running and reset it whenever the user does something?Belong
D
12
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);
      }
    }
Disarm answered 18/11, 2010 at 0:15 Comment(9)
@Nappy: Then please explain the right way of doing this. Your comment is vague and indecisive.Disarm
@AKh: The other answers already show the possibilities. In your solution, I cannot see any benefit from polling every 15 seconds. It would have the same effect, as you start a timer on "ACTION_SCREEN_OFF" with a random duration from 0-15 seconds. This just does not make sense..Rabbinism
@Nappy: Every 15 sec I not only check for SCREEN_ON or SCREEN_OFF but also for user's last interaction time and App foreground status. Based on these three factors I make a logical decision on how active the user is interacting with the app.Disarm
Please complete your comment. ...."if your isScreenof boolean is ?" And also the app foregrnd status has to be taken into account.Disarm
It would make sense to me, if you only reset the timer in onUserInteraction (Fredriks answer), if your isScreenOff boolean is true, and skip the polling part completley. If I read your last comment correctly, you missed the important part in the code above?Rabbinism
I updated the code now. Hope things looks better now. If not let me know.Disarm
so you are still updating lastInteractionTime in onUserInteraction, so you dont need to reset a timer, which you think is much more costful. ok - I got that. I was confused because in your code, you only polled the screenOff boolean, for which polling was unnecessary.Rabbinism
This code is full with mistakes, some variable aren't initialize.Ocotillo
SCREEN_ON or SCREEN_OFF might happen when the user receive a missed call even, so you should provide code for isInForeGrndEffieeffigy
K
121

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();
    }
}
Kiangsi answered 18/9, 2012 at 17:18 Comment(13)
This will create multiple instances of the Handler and Runnable for each Activity created. If we convert these two members to static, this will be avoided. Also, could you tell me why have you called stopDisconnectTimer() in onStop()?`Peyton
@Gaurav In my case, this is only implemented in one activity (therefore I didn't catch the issue with the static modifier). As for the onStop(), from what I remember, I call onBackPressed() in order to return to a login screen in the disconnect callback which in turn calls the onStop() method. When the user returns to the login screen manually, by pressing back, the timer needs to be stopped as well thus the stopDisconnectTimer() in onStop(). I guess this part depends on your needs and implementation.Kiangsi
@Kiangsi is it possible to redirect the user to the login activity?Edelmiraedelson
@Apostrifix, Of course it is possible. In my case there was only one activity : calling onBackPressed() vas sufficient. If you have more than one activity in your stack, you just have to create an intent for that matter. You may want to look at the following answer in order to clear the Activity task (and prevent users from re-connecting on a back): #7075849Kiangsi
Great Work! I added getter and setter for the runnable and then set it in the extending class as needed using the onCreate method...perfect, again thank you.Raspberry
This works even when app is in usage. How can I overcome that?Gay
@gfrigon..The above methods are working fine after login and working. suppose, in between working activities, if we go to open another app, timer as stopped. that time also need to auto logout means. what we need to do? I have tried with onPause with manual timer schedular, but, its affecting regular functionality timeout. can you suggest anythings ?Ramon
@Ramon I suggest reading about the Activity Life cycle: developer.android.com/guide/components/activities/…. You may want to hook starting and stopping your timer at different places in your activity lifecyle. You may want to add a different timer that tracks the time your application is not in foregroud (the time between a onPause and a onResume) and logging out on an onResume if your timer is expired. If you need more specific support, You may want to post a question with detailed problem description.Kiangsi
@GauravBhor, if we make the Handler and Runnable static, how can we create a new Intent(CurrentActivity.this, MainActivity.class) and startActivity(intent) from within the Runnable, as CurrentActivity.this and startActivity() cannot be referenced from a static context?Hypnotherapy
@MJM you may want to hook prints to the life cycle methods (onStop, onPause, onResume, onStart) to see what is called when you lock and unlock your device. If any of these is called when you lock your device, you may want to see if your timer is expired before you call resetDisconnectTimer();Kiangsi
What about a schedule of every 5min to check the last user interaction (that can be managed by storing timestamp coming from onUserInteraction). And you avoid multiple calls to reset the counter/handlerSyndesis
at Android 8 OREO it causing "sending message to a Handler on a dead thread" errorSlogan
I just learned about the onUserInteraction override, a solution to a completely different problem I was facing. Thanks for answering a decade ago mister.Stegman
T
110

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.

Thai answered 17/11, 2010 at 23:11 Comment(11)
Yeah this is one way of doing it... but my app has 30 different activities and there would be too much interaction when the user is active... so every time resetting the timer wud be a costly operation... which at the worst case can 50 to 60 times in a minute.Disarm
I haven't timed it but I'd say resetting a timer like this lastInteraction = System.currentTimeMillis(); would take, say, 2 ms. Do it 60 times a minute and you "loose" 120ms. Out of 60000.Thai
Fredrik... I am using your suggestion as well to meet this scenario.. Screen timeout is set to the max 30 min on the device. MyApp shd timeout after 15 mins...If user doesnt touch anything on the screen from more more than 1 min then I will start the 15min Logout timer.... In this case I would check the differenct(lastInteractionTime and System.currentTimeMills()) is more than 1 min... then fire..Disarm
onUserInteraction() is not called in some instances however (dialogs do not call it, and scrolling in spinners) is there a work around for these situations?Stockton
could u share your MyTimerClass?Erk
What is the implementation for MyTimerClassDowndraft
Can you share implementation for MyTimerClass?Nip
Excellent contribution! It is what I was looking for. Thank you!!Protamine
shouldn't you call super.onUserInteraction(); ?Thereof
This worked for me, it may not reset the timer for dialogs etc. but if a user is spending 5 minutes staring at a dialog you have bigger problems. I didn't even add it to every activity, just the main ones that get traversed often and the ones where they are likely to spend the most time.Gothurd
Thanks. Also, is there a way to detect key input events from soft (on-screen) keyboard?Tasteful
G
24

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 
}
Gemsbok answered 1/2, 2017 at 9:55 Comment(1)
You saved my life with onUserInteractionDisproof
D
12
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);
      }
    }
Disarm answered 18/11, 2010 at 0:15 Comment(9)
@Nappy: Then please explain the right way of doing this. Your comment is vague and indecisive.Disarm
@AKh: The other answers already show the possibilities. In your solution, I cannot see any benefit from polling every 15 seconds. It would have the same effect, as you start a timer on "ACTION_SCREEN_OFF" with a random duration from 0-15 seconds. This just does not make sense..Rabbinism
@Nappy: Every 15 sec I not only check for SCREEN_ON or SCREEN_OFF but also for user's last interaction time and App foreground status. Based on these three factors I make a logical decision on how active the user is interacting with the app.Disarm
Please complete your comment. ...."if your isScreenof boolean is ?" And also the app foregrnd status has to be taken into account.Disarm
It would make sense to me, if you only reset the timer in onUserInteraction (Fredriks answer), if your isScreenOff boolean is true, and skip the polling part completley. If I read your last comment correctly, you missed the important part in the code above?Rabbinism
I updated the code now. Hope things looks better now. If not let me know.Disarm
so you are still updating lastInteractionTime in onUserInteraction, so you dont need to reset a timer, which you think is much more costful. ok - I got that. I was confused because in your code, you only polled the screenOff boolean, for which polling was unnecessary.Rabbinism
This code is full with mistakes, some variable aren't initialize.Ocotillo
SCREEN_ON or SCREEN_OFF might happen when the user receive a missed call even, so you should provide code for isInForeGrndEffieeffigy
C
12
@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));
}
Cloyd answered 12/6, 2013 at 12:7 Comment(3)
This is the base of the solution, the rest can be modified depending on your partiular needs and application architecture complexity! Thanks for the answer!Resuscitate
How to apply this in application classOptimist
The concise solution ever! Thanks a ton man!Faunie
B
7

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.

Beaumarchais answered 17/11, 2010 at 21:46 Comment(0)
D
4

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.

Differentiable answered 23/3, 2016 at 8:49 Comment(1)
Best/simplest answer here thanks for thatFortify
K
4

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
}
Katowice answered 22/1, 2019 at 14:16 Comment(0)
W
3

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");

    }

}
Wildebeest answered 24/8, 2018 at 4:45 Comment(0)
A
2

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();
Aultman answered 16/1, 2013 at 15:19 Comment(1)
how to check user is inactive ?? any input from system?Raffia
I
2

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>
Irksome answered 15/9, 2019 at 16:48 Comment(0)
S
0

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
Sculptress answered 6/11, 2014 at 23:11 Comment(0)
D
0

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

Downwards answered 13/7, 2018 at 15:37 Comment(0)
D
0

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);
}
Doer answered 5/9, 2018 at 17:50 Comment(0)
B
0
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.

Brittle answered 30/10, 2019 at 13:54 Comment(0)
C
0

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

Creath answered 18/8, 2021 at 11:58 Comment(0)
P
0

So based on the top answer and the comments:

public class SActivity extends AppCompatActivity {
    public static Runnable dcCallback = null;
    public static long dcTimeout = 60000;
    private static Handler dcHandler = new Handler();

    public static void resetDisconnectTimer() {
        dcHandler.removeCallbacks(dcCallback);
        dcHandler.postDelayed(dcCallback, dcTimeout);
    }

    public static void stopDisconnectTimer() {
        dcHandler.removeCallbacks(dcCallback);
    }

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

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

Here's some cases that I think are worth mentioning:

  1. AlertDialog shows and onUserInteraction() no longer works? -> Since we made resetDisconnectTimer() static, we can just call this anywhere in our activity (for example you'd need to add addTextChangedListener to your AlertDialog's EditTexts and call it there)
  2. Multiple instances of your LockscreenActivity is shown after app automatically gets locked? or your app has an option that allows user to lock the app manually? -> just like the previous case, call stopDisconnectTimer() before launching LockscreenActivity.
  3. Remember to design your LockscreenActivity someway that checks if app has been locked internally or it's the first time user is trying to login; and if it has been locked by the app itself then override onBackPressed and close the app so you don't end up with garbage security :P Here's an example on how to do so:
// In LockscreenActitivty's onCreate:
innerLock = getIntent().getBooleanExtra("innerLock", false);

@Override
public void onBackPressed() {
    if (innerLock) finishAffinity();
    else super.onBackPressed();
}

So basically extend the above class to all your activities that you want to be secured (except lockscreen one ofc). And remember since we made the Runnable static, we have to initialize it through our very first activity that extends SActivity (the one that opens after user logs in). It'd look something like this:

dcCallback = () -> {
    stopDisconnectTimer();
    startActivity(new Intent(context, LockscreenActivity.class).putExtra("innerLock", true))
};

And you can access and change the dcTimeout directly in any activity that extends SActivity

Although it might be way too late and there might be some better solutions than this, but hope this helps someone :)

Penna answered 10/3, 2023 at 12:58 Comment(0)
E
0

override method dispatchTouchEvent() in your activity
and you will be notified about every touch in your app

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    ev?.let { event ->
        if (event.action == ACTION_DOWN) {
            //start counting user's inactivity
            //or launch a coroutine with delay before screen dimming
        }
    }
    return super.dispatchTouchEvent(ev)
}

I launched a coroutine with delay() before inactive-action from there

Eyelash answered 31/7, 2023 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.