7

I am developing an application that needs to display a passcode screen whenever a user leaves the app and comes back (be it through a screen lock, or going back to the home screen through the back or home button). I had it working using the following:

The starting activity would call for the passcode check on startup, and each activity added the following functionality to their onPause method:

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

    if (!isFinishing()) {
    new PasscodeCheckTask(this.getApplicationContext(),this).execute();
    }
}

The PassocdeCheckTask looks like the following. It checks to see if the screen is off or the app is no longer in the background

public class PasscodeCheckTask extends AsyncTask<Void, Void, Boolean> {

    public static final int CHECK_PASSCODE = 0;

    private Context mActivityApplicationContext;
    private Context mActivityContext;

    public PasscodeCheckTask(Context applicationContext, Context activityContext){
        mActivityApplicationContext = applicationContext;
        mActivityContext = activityContext;
    }

    @Override
    protected Boolean doInBackground(Void... params) {
        Boolean result = false;

        if (!((PowerManager)mActivityApplicationContext.getSystemService(android.content.Context.POWER_SERVICE)).isScreenOn() ||
            !isAppOnForeground(mActivityApplicationContext)) {
            result = true;
        }
        return result;
    }

    @Override
    protected void onPostExecute(Boolean result) {
        if (result) {
            // Start passcode activity to check for passcode
            /* CODE HERE */
            ((Activity)mActivityContext).startActivityForResult(intent, CHECK_PASSCODE);
        }
    }

    protected boolean isAppOnForeground(final Context context) {
        List<RunningAppProcessInfo> appProcesses = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getRunningAppProcesses();

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

        final String packageName = context.getPackageName();
        for (RunningAppProcessInfo appProcess : appProcesses) {
            if ((appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) && 
                 appProcess.processName.equals(packageName)) {
                return true;
            }
        }
        return false;
    }
}

The Passcode activity would finish when done, and the calling activity would moveTaskToBackground(true) if the passcode didn't pass. This system worked beautifully until I tried it on an HTC Evo with mikg ROM. For some reason, the appProcess.importance never showed up as IMPORTANCE_FOREGROUND. It was always IMPORTANCE_BACKGROUND. Thus, the passcode would ALWAYS be brought up, even though the app never went into the background.

I tried DropBox on that phone (which has a passcode lock as well), and it worked beautifully. I can't seem to find a different way to know when an app has gone to the background, or if it is being brought back from the background. Any ideas on how to make this work?

dmon
  • 30,048
  • 8
  • 87
  • 96
Chewie
  • 125
  • 2
  • 7
  • 1
    You really shouldn't need to do something like this. What if your user locks their phone while using your app? Then they have to do their lockscreen, and then your lockscreen. I might get chastised for this, but the android lock screen is enough. If you're doing something so important that you need to have your own lock screen, perhaps you should reconsider even putting it on a phone. – Kurtis Nusbaum Nov 23 '11 at 22:05
  • It is necessary and deals with the user's private information. I understand the concern, but a passcode is useful for this. – Chewie Nov 26 '11 at 03:08
  • that's kind of a weird comment @KurtisNusbaum. I use Dropbox on my phone, and I have a lot of very sensitive personal information stored. As most Dropbox accounts do I guess. You can't be really serious when you say it is not a good idea to protect such apps with an password. There are numurous cases when a password is a very, very wise idea, if not obligatory, one might even argue. I think even the Gallery app should have a password, I'm kind of keen on my privacy, and people tend to play with eachothers phones. – slinden77 May 31 '13 at 08:53
  • @dmmh The balance between security and ease of use is a really delicate dance on mobile platforms. On the one hand, you still want to protect privacy and be safe, and on the other, mobile apps should be quick to get in and out of. This is my particular take on this situation. I think arguments can be made for both sides. My main point is actually, when you're fighting against the system, sometimes it's a good idea to take a step back and ask yourself "wait, is what I'm trying to do even reasonable or what I want to actually accomplish." – Kurtis Nusbaum Jun 08 '13 at 21:38
  • I do agree with that completely :) Then again, people also tend to not pin lock their phones because they find it annoying. In that case they might prefer to have pin protection on some of their apps. And password protection inside apps ofcourse should be optional, not obligatory. That is, if the sole purpose of the app is NOT to hide sensitive data, because then it would be a requirement ;) – slinden77 Jun 10 '13 at 10:15

2 Answers2

4

In onStop() of each activity, update a static data member with the time you left the activity. In onStart() of each activity, check that time, and if it exceeds some timeout threshold, display your authentication activity. Allow the user to set the timeout value, so that if they don't want to be bothered every few seconds, they can control that.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
1

I liked the time based approach, I've been struggling for a while getting this to work in a nice way. The time based approach works well. I made a class for easier usage.

public class PinCodeCheck {

    private static long INIT_TIME           = 0;
    private static PinCodeCheck ref         = null;
    private static SharedPreferences values = null;

    private PinCodeCheck(Context context){
        values = context.getSharedPreferences("com.example.xxx", 0); //use your preferences file name key!
    }//end constructor

    public static synchronized PinCodeCheck getInstance(Context context) {
        if (ref == null){
            ref = new PinCodeCheck(context);
        } 
        return ref;
    }//end method 

    public void init(){
        PinCodeCheck.INIT_TIME = System.currentTimeMillis();    
    }//end method   

    public void forceLock(){
        PinCodeCheck.INIT_TIME = 0;     
    }//end method   

    public boolean isLocked(){
        long currentTime    = System.currentTimeMillis();
        long threshold      = values.getLong(Keys.PASSWORD_PROTECT_TIMEOUT, 30000); // check here, might change in between calls
        if (currentTime - PinCodeCheck.INIT_TIME > threshold){
            return true;
        }
        return false;       
    }//end method   
}//end class

USAGE

private static PinCodeCheck check   = PinCodeCheck.getInstance(context);

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

    if (check.isLocked()) { 
        showDialog();               
    }
}//end method 

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

    check.init();
}//end method 
slinden77
  • 3,378
  • 2
  • 37
  • 35
  • Hi, I am trying your approach for one of my app. But i am getting error in this line. values = Preferences.init(context); as "The method init(Context) is undefined for the type Preferences" – dhiku Nov 06 '13 at 18:48
  • ah yes, that's a rerefence to my custom `SharedPreferences` class. I'll edit example... – slinden77 Nov 06 '13 at 19:21