1

I need to know to detect when the android device has been idle for a certain period of time when my app is currently in the background (and if it is idle, bring my app to the front). The only two ways I could think of to do this are:

  1. Somehow detect user interaction outside of the app, and if there hasn't been any input for X number of minutes, bring my app to the front.

    or:

  2. When the device goes into sleep mode, bring my app to the front.

I can't figure out how to either of these, but 2 seems like the most feasible option to me. What would be the code for this?

uyuyuy99
  • 353
  • 4
  • 11
  • I have seen similar discussions around before e.g. here: http://stackoverflow.com/questions/4075180/application-idle-time – Scalarr Oct 09 '14 at 21:26
  • @Scalar That's for detecting idle time _inside_ the application. – uyuyuy99 Oct 09 '14 at 21:48
  • When the device goes into sleep mode, *nothing* (at least in the way of ordinary software) is running. – Chris Stratton Oct 09 '14 at 21:54
  • @ChrisStratton I realize that, which is exactly my problem. However, maybe there's a way to override some sort of "screen off" event and make it open my app (or something along those lines)? – uyuyuy99 Oct 09 '14 at 22:39
  • http://stackoverflow.com/questions/6582018/how-to-register-to-sleep-event-in-android but what they are actually doing is screen off, which might happen before actual sleep. – Chris Stratton Oct 09 '14 at 23:06
  • Ahh, I see, perhaps one possibility might be to make use of the built in alarm system then? E.g. keep setting an alarm to do something X minutes onwards. – Scalarr Oct 09 '14 at 23:13

1 Answers1

2

I was able to accomplish 1. by using "transparent" view which was placed on top of all views, which checks for user touches

Steps for achieving the needed effect:

1) Create a Service which in its onCreate method creates the transparent view and attach it to the views stack
2) onStartCommand call the initTimer() method
3) implement View.OnTouchListener on the service
4) override onTouch method
5) onTouch event received - call initTimer()
6) on onDestroy of the service - remove the transparent view from the views stack
7) start the service when onPause of your main activity is called
8) stop the service when the app opens

Here is the code:

private Handler mHandler;
private Runnable mRunnable;
private final int mTimerDelay = 60000;//inactivity delay in milliseconds
private LinearLayout mTouchLayout;//the transparent view

@Override
public IBinder onBind(Intent intent) {
    return null;
}

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

    mTouchLayout = new LinearLayout(this);

    LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT);
    mTouchLayout.setLayoutParams(lp);

    // set on touch listener
    mTouchLayout.setOnTouchListener(this);

    // fetch window manager object
    WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
    // set layout parameter of window manager

    WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
                    WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT
    );

    mParams.gravity = Gravity.LEFT | Gravity.TOP;
    windowManager.addView(mTouchLayout, mParams);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    initTimer();

    return START_NOT_STICKY;
}

@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
    Log.d("IdleDetectorService", "Touch detected. Resetting timer");
    initTimer();
    return false;
}

@Override
public void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacks(mRunnable);
    WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
    if (windowManager != null && mTouchLayout != null) {
        windowManager.removeView(mTouchLayout);
    }
}

/**
 * (Re)sets the timer to send the inactivity broadcast
 */
private void initTimer() {
    // Start timer and timer task
    if (mRunnable == null) {

        mRunnable = new Runnable() {
            @Override
            public void run() {
                Log.d("IdleDetectorService", "Inactivity detected. Sending broadcast to start the app");

                try {
                    boolean isInForeground = new ForegroundCheckTask().execute(getApplicationContext()).get();

                    if (!isInForeground) {
                        Intent launchIntent = getApplication()
                                .getPackageManager()
                                .getLaunchIntentForPackage("<your-package-name>");
                        if (launchIntent != null) {
                            LogUtil.d("IdleDetectorService", "App started");
                            getApplication().startActivity(launchIntent);
                        }
                    }

                    stopSelf();
                } catch (Exception e) {
                }
            }
        };
    }

    if (mHandler == null) {
        mHandler = new Handler();
    }

    mHandler.removeCallbacks(mRunnable);
    mHandler.postDelayed(mRunnable, mTimerDelay);
}

private class ForegroundCheckTask extends AsyncTask<Context, Void, Boolean> {

    @Override
    protected Boolean doInBackground(Context... params) {
        final Context context = params[0];
        return isAppOnForeground(context);
    }

    private boolean isAppOnForeground(Context context) {
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses = null;

        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
            appProcesses = activityManager.getRunningAppProcesses();
        } else {
            //for devices with Android 5+ use alternative methods
            appProcesses = AndroidProcesses.getRunningAppProcessInfo(getApplication());
        }

        if (appProcesses == null) {
            return false;
        }

        final String packageName = context.getPackageName();

        for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
            if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
                    appProcess.processName.equals(packageName)) {
                return true;
            }
        }

        return false;
    }
}

Note that you should add additional permission in your AndroidManifest.xml file: android:name="android.permission.SYSTEM_ALERT_WINDOW"

  • Thank you! It works for me with the following: `val mParams = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL, PixelFormat.TRANSLUCENT )` – Skander Hamdi May 16 '21 at 22:25