118

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.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Akh
  • 5,961
  • 14
  • 53
  • 82

18 Answers18

120

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();
    }
}
Jorge Gil
  • 4,265
  • 5
  • 38
  • 57
gfrigon
  • 2,237
  • 2
  • 23
  • 32
  • 4
    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()`?` – Gaurav Bhor Aug 27 '14 at 05:06
  • @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. – gfrigon Aug 27 '14 at 13:49
  • @gfrigon is it possible to redirect the user to the login activity? – Apostrofix Nov 12 '15 at 11:49
  • @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): http://stackoverflow.com/questions/7075349/android-clear-activity-stack – gfrigon Nov 12 '15 at 22:03
  • 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. – CrandellWS Jun 04 '16 at 16:40
  • This works even when app is in usage. How can I overcome that? – Aparajita Sinha Feb 15 '17 at 08:42
  • @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 ? – harikrishnan Oct 12 '17 at 07:29
  • @harikrishnan I suggest reading about the Activity Life cycle: https://developer.android.com/guide/components/activities/activity-lifecycle.html. 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. – gfrigon Oct 20 '17 at 17:29
  • 1
    @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? – Tur1ng Jan 11 '18 at 14:02
  • @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(); – gfrigon Jun 05 '18 at 19:16
  • 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/handler – rafaelasguerra Oct 02 '19 at 11:50
  • at Android 8 OREO it causing "sending message to a Handler on a dead thread" error – appukrb Sep 22 '20 at 16:27
  • I just learned about the onUserInteraction override, a solution to a completely different problem I was facing. Thanks for answering a decade ago mister. – lucster Dec 09 '22 at 12:10
108

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.

CaptJak
  • 3,592
  • 1
  • 29
  • 50
Fredrik Wallenius
  • 2,872
  • 2
  • 20
  • 15
  • 1
    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. – Akh Nov 17 '10 at 23:37
  • 3
    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. – Fredrik Wallenius Nov 17 '10 at 23:39
  • 1
    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.. – Akh Nov 18 '10 at 01:13
  • 4
    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? – AndroidNoob Apr 25 '14 at 11:00
  • could u share your MyTimerClass? – Sibelius Seraphini Jun 20 '16 at 18:46
  • What is the implementation for `MyTimerClass` – McSullivan Oct 04 '16 at 13:52
  • Can you share implementation for MyTimerClass? – Vincent Paing Apr 28 '18 at 04:16
  • Excellent contribution! It is what I was looking for. Thank you!! – Oscar Ordoñez Mego Apr 09 '19 at 07:57
  • shouldn't you call super.onUserInteraction(); ? – Mike W Jul 15 '19 at 13:28
  • 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. – LordWabbit Nov 25 '20 at 05:58
  • Thanks. Also, is there a way to detect key input events from soft (on-screen) keyboard? – Chandler Feb 17 '22 at 09:43
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 
}
Pradeep Gupta
  • 535
  • 5
  • 10
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));
}
soenke
  • 1,023
  • 1
  • 8
  • 8
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);
      }
    }
Khemraj Sharma
  • 57,232
  • 27
  • 203
  • 212
Akh
  • 5,961
  • 14
  • 53
  • 82
  • 5
    @Nappy: Then please explain the right way of doing this. Your comment is vague and indecisive. – Akh May 03 '11 at 17:08
  • 2
    @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.. – Nappy May 03 '11 at 17:32
  • 1
    @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. – Akh May 03 '11 at 18:27
  • Please complete your comment. ...."if your isScreenof boolean is ?" And also the app foregrnd status has to be taken into account. – Akh May 03 '11 at 22:33
  • 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? – Nappy May 03 '11 at 22:37
  • I updated the code now. Hope things looks better now. If not let me know. – Akh May 03 '11 at 22:42
  • 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. – Nappy May 03 '11 at 23:08
  • 1
    This code is full with mistakes, some variable aren't initialize. – Big.Child Mar 05 '13 at 08:02
  • SCREEN_ON or SCREEN_OFF might happen when the user receive a **missed call** even, so you should provide code for `isInForeGrnd` – Chathura Wijesinghe Jan 20 '14 at 03:45
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.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
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.

ElOjcar
  • 301
  • 2
  • 4
  • 12
A_rmas
  • 784
  • 2
  • 10
  • 26
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");

    }

}
damith alahakoon
  • 270
  • 4
  • 14
3

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
}
Community
  • 1
  • 1
Hitesh Sahu
  • 41,955
  • 17
  • 205
  • 154
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();
divide by zero
  • 2,340
  • 5
  • 23
  • 34
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>
Oyewo Remi
  • 406
  • 5
  • 8
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
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

chebaby
  • 7,362
  • 50
  • 46
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);
}
box
  • 4,450
  • 2
  • 38
  • 38
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.

Tom Peak
  • 11
  • 5
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

ʀᴀʜɪʟ
  • 235
  • 1
  • 4
  • 8
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 :)

Arezz
  • 1
  • 2
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

Kirguduck
  • 748
  • 1
  • 9
  • 20