103

This has been driving me nuts for a while now.

Is there any way of reliably detecting if the home button has been pressed in an android application?

Failing that, is there a robust way of telling what caused an activity to go into onPause? i.e Can we detect if it was caused by a new activity launching or by pressing back/home.

One suggestion I have seen is to override onPause() and call isFinishing() but this will return false when pressing the home button just as it would if a new activity is starting so this fails to distinguish between the two.

Any help much appreciated.

** Update** : Thanks to @android-hungry for this link: https://nishandroid.blogspot.com/

Overiding the following method:

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);           
}

Then the following event WILL get fired for home button presses:

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {     

    if(keyCode == KeyEvent.KEYCODE_HOME)
    {
       //The Code Want to Perform. 
    }
});

I'm not sure if there are any side effects with this line:

this.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD);   

So it would seem that contrary to popular belief, you can in fact listen out for the home key. Worryingly, you can return false and have the home key do nothing.

Update: As expected, there are some side affects with this - it seems that embedded videos and google maps are not visible with this mode enabled.

Update: Supposedly this hack no longer works as of Android 4.0 onwards

nisha.113a5
  • 2,024
  • 16
  • 30
Dean Wild
  • 5,886
  • 3
  • 38
  • 45
  • My problem wasn't to disguise between back and home -key but I wanted to finish application on both cases. Which I did using ``Activity.onUserLeaveHint()``. – harism Jan 16 '12 at 15:20
  • The only problem is onUserLeaveHint() will also fire when I start an activity from said activity, I only want to know if back or home has been pressed. Thanks for suggestion though – Dean Wild Jan 16 '12 at 15:23
  • That's true, but unfortunately, as far as I know, it's the only place to receive any information on Home -key usage. Making it more of a problem to harvest out false -positives, out of many can be recognized easily, but still making easy sounding task rather complicated. – harism Jan 16 '12 at 15:36
  • Yes, i've got round it previously by setting a flag whenever I start activities and checking for this flag in the onPause() so I know if it was caused by a new intent or by a homekkey press. I was looking for a more elegant solution – Dean Wild Jan 16 '12 at 15:41
  • 2
    @DeanWild: did u read this: http://nisha113a5.blogspot.com/ – Pratik Bhat Jan 16 '12 at 16:35
  • @android_hungry - im stunned but this actually works. – Dean Wild Jan 16 '12 at 16:49
  • @DeanWild: i l post this answer , so that others find it useful ! cheers – Pratik Bhat Jan 16 '12 at 16:56
  • its not working in ICS – sravan Aug 19 '13 at 05:16
  • This no longer works as of 4.0. The last comment on this post: https://groups.google.com/forum/#!topic/android-developers/trRI99-HszQ – japino Nov 14 '13 at 17:24
  • 2
    TYPE_KEYGUARD constant was removed from WindowManager.LayoutParams in Android 5.0 – theb1uro Feb 15 '15 at 20:34
  • The average app can get along fine without needing to know the difference between a Home press and a pause event. In other words, onPause() is usually sufficient. Why do you want to do this? Giving us more information about your intentions could lead to a better overall strategy for you. – Josh Jan 16 '12 at 15:54
  • Hi Josh, I need the app to relaunch from scratch everytime it is opened. So if the appllication is killed from a backkey press - no problem. But if the user presses the homekey and then relaunches, it resumes. I am fully aware that from a user-experience point of view this is bad and it goes against the android application lifecycle completely but unfortunately my client wants it and they won't budge. – Dean Wild Jan 16 '12 at 16:00
  • So many things wrong with this I don't know where to start. 1) There are many ways to get to an app (like returning to it after answering a phone call), so "every time it is opened" doesn't make sense unless you define all of those transitions. 2) Are you saying you want to achieve your "always relaunch" strategy by killing the app any time it is not visible on screen? 3) How does differentiating between Home and Back (or any other onPause event) help your cause? – Josh Jan 16 '12 at 16:14
  • 4) Your client may "think" they want the app relaunched from scratch all the time, but they probably just don't know how mobile apps work. – Josh Jan 16 '12 at 16:16
  • Don't mean to sound rude, just trying to point out the potential flaws and hopefully convince you you're going down the wrong path. – Josh Jan 16 '12 at 16:18
  • 1) Whenever the app is launched from it's icon, it needs to start from scratch. So if it is coming back from a phonecall etc it can resume as it normally does. 2) No, I want to kill the app when the back is pressed and when the home key is pressed, NOT when I launch a new activity. 3)I don't need to differentiate between home and back, I need to differentiate between home/back and new intents - if the pause is caused by a new intent I DON'T want to finish the activity - if it is caused by backbutton or home key I DO want to finish the activity. – Dean Wild Jan 16 '12 at 16:25
  • 4) Indeed, and this app has to mirror an iPhone app pixel for pixel. So I have on screen back buttons etc, it's sacrilegious but I cannot convince them otherwise. – Dean Wild Jan 16 '12 at 16:25
  • Haha, ok, at least we know that you're not the crazy one now. Check out my other answer for possible solutions. – Josh Jan 16 '12 at 16:38

18 Answers18

155

Following code works for me :)

HomeWatcher mHomeWatcher = new HomeWatcher(this);
mHomeWatcher.setOnHomePressedListener(new OnHomePressedListener() {
    @Override
    public void onHomePressed() {
        // do something here...
    }
    @Override
    public void onHomeLongPressed() {
    }
});
mHomeWatcher.startWatch();
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;

public class HomeWatcher {

    static final String TAG = "hg";
    private Context mContext;
    private IntentFilter mFilter;
    private OnHomePressedListener mListener;
    private InnerReceiver mReceiver;

    public HomeWatcher(Context context) {
        mContext = context;
        mFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    }

    public void setOnHomePressedListener(OnHomePressedListener listener) {
        mListener = listener;
        mReceiver = new InnerReceiver();
    }

    public void startWatch() {
        if (mReceiver != null) {
            mContext.registerReceiver(mReceiver, mFilter);
        }
    }

    public void stopWatch() {
        if (mReceiver != null) {
            mContext.unregisterReceiver(mReceiver);
        }
    }

    class InnerReceiver extends BroadcastReceiver {
        final String SYSTEM_DIALOG_REASON_KEY = "reason";
        final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
        final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps";
        final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey";

        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
                String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY);
                if (reason != null) {
                    Log.e(TAG, "action:" + action + ",reason:" + reason);
                    if (mListener != null) {
                        if (reason.equals(SYSTEM_DIALOG_REASON_HOME_KEY)) {
                            mListener.onHomePressed();
                        } else if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) {
                            mListener.onHomeLongPressed();
                        }
                    }
                }
            }
        }
    }
}
public interface OnHomePressedListener {
    void onHomePressed();
    void onHomeLongPressed();
}
Jack
  • 1,559
  • 1
  • 9
  • 4
  • 4
    Your `onHomeLongPressed` actually seems to correspond to the opening of the "Recents" system activity. On my phone, that's triggered by pressing the recents button next to the home button, so your code's assumption about it being a home long press isn't always correct. – Sam May 14 '15 at 22:43
  • why it don't work for me, i did exact same except registered the broadcast through manifest. – Farhan Jun 02 '15 at 07:41
  • Registered in application class, Working so far.. +1, I wonder whats the catch? I mean, what original case would we be missing.. :^) – Farhan Jun 02 '15 at 07:52
  • 1
    sometimes intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); return null. I would like to know what it's happening ?? – Fakher Feb 10 '16 at 09:36
  • Still works fine, but for those interested, LONG_PRESS is handled a bit differently. – Shark Mar 04 '16 at 13:01
  • Is it possible to do this from a service while the screen is locked? This code uses the ACTION_CLOSE_SYSTEM_DIALOGS filter which doesn't work while the screen is off. – Rosenpin Sep 02 '16 at 19:21
  • I managed to do this by starting a dummy activity and show it on the lockscreen, it isn't perfect but it works for my needs – Rosenpin Sep 02 '16 at 19:37
  • I have the same issue as @Fakher My IDE doesn't even show it in the hints – DBX12 Sep 19 '16 at 14:55
  • 1
    The long press reason is now called `final String SYSTEM_DIALOG_REASON_LONG_PRESS = "assist" ` – JWqvist Sep 29 '16 at 09:04
  • Thanks a lot. It's working, just add `voiceinteraction` on long press event. – Chirag Savsani Jun 13 '17 at 11:58
  • It is working great in all devices except Samsung devices (example Samsung J7), Does anyone knows specific reason or solution? – buzzingsilently Sep 08 '17 at 05:20
  • It works. Awesome. This can detect home and recent app menu click from any app – Ajay Shrestha Oct 24 '17 at 21:14
  • Omg thank you. I was able to make react native module out of this! Let me know if you'd like to contribute. https://www.npmjs.com/package/react-native-home-pressed – evanjmg Feb 24 '18 at 20:27
  • Perfect solution for me also, saved my day :) – Ramkesh Yadav Dec 18 '18 at 12:28
  • Ok, it detects the click on Home button but it does not prevent from going to the home screen (springboard)... Any solution to avoid going back to springboard? regards. – toto_tata Feb 26 '19 at 15:33
  • This solution works. But it does not prevent the app from going into background. How do i avoid that? – user2511882 May 14 '19 at 15:38
  • Its working, but how can I prevent the close app when the home button is pressed? – Ferchi Jul 01 '19 at 14:17
  • @Ferchi : Did you find a solution on how to prevent closing app? – KMC Sep 12 '19 at 10:16
  • Thanks a lot, works like a charm. For gesture navigation on Android Q however, the onHomeLongPressed was triggered instead of onHomePressed when swiping up to home. I used this answer (https://stackoverflow.com/questions/56689210/how-to-detect-full-screen-gesture-mode-in-android-10) for a workaround, detecting what type of navigation is used and acting accordingly. Thought it might be useful to some. – Jorn Rigter Aug 05 '20 at 11:02
  • ACTION_CLOSE_SYSTEM_DIALOGS - This constant was deprecated in API level 31. – Sankhadeep Biswas May 03 '22 at 18:39
57

This is an old question but it might help someone.

@Override
protected void onUserLeaveHint()
{
    Log.d("onUserLeaveHint","Home button pressed");
    super.onUserLeaveHint();
}

According to the documentation, the onUserLeaveHint() method is called when the user clicks the home button OR when something interrupts your application (like an incoming phone call).

This works for me.. :)

AL̲̳I
  • 2,441
  • 4
  • 29
  • 50
  • 33
    Not Correct !!! That is worked at home button pressed but it is also worked when Swiching Activity with Intent!! – Nikunj Paradva Sep 10 '16 at 11:02
  • 1
    This will execute before onStop() always, even if other activity comes on the top or user forcefully leave the activity or clicking home button... – Navas pk Nov 17 '18 at 07:13
  • 1
    @NikunjParadva You can use onBackPressed() method in other activities to solve this issue. – zinonX Aug 06 '21 at 09:58
7

I needed to start/stop background music in my application when first activity opens and closes or when any activity is paused by home button and then resumed from task manager. Pure playback stopping/resuming in Activity.onPause() and Activity.onResume() interrupted the music for a while, so I had to write the following code:

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

  // start playback here (if not playing already)
}

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

  ActivityManager manager = (ActivityManager) this.getSystemService(Activity.ACTIVITY_SERVICE);
  List<ActivityManager.RunningTaskInfo> tasks = manager.getRunningTasks(Integer.MAX_VALUE);
  boolean is_finishing = this.isFinishing();
  boolean is_last = false;
  boolean is_topmost = false;
  for (ActivityManager.RunningTaskInfo task : tasks) {
    if (task.topActivity.getPackageName().startsWith("cz.matelier.skolasmyku")) {
      is_last = task.numRunning == 1;
      is_topmost = task.topActivity.equals(this.getComponentName());
      break;
    }
  }

  if ((is_finishing && is_last) || (!is_finishing && is_topmost && !mIsStarting)) {
    mIsStarting = false;
    // stop playback here
  }
}

which interrupts the playback only when application (all its activities) is closed or when home button is pressed. Unfortunatelly I didn't manage to change order of calls of onPause() method of the starting activity and onResume() of the started actvity when Activity.startActivity() is called (or detect in onPause() that activity is launching another activity other way) so this case have to be handled specially:

private boolean mIsStarting;

@Override
public void startActivity(Intent intent) {
  mIsStarting = true;
  super.startActivity(intent);
}

Another drawback is that this requires GET_TASKS permission added to AndroidManifest.xml:

<uses-permission
  android:name="android.permission.GET_TASKS"/>

Modifying this code that it only reacts on home button press is straighforward.

Blackhex
  • 1,694
  • 3
  • 21
  • 45
7

It is impossible to detect and/or intercept the HOME button from within an Android app. This is built into the system to prevent malicious apps that cannot be exited.

Jave
  • 31,598
  • 14
  • 77
  • 90
  • check the accepted answer, it is possible. Not tested on many devices yet though. – Dean Wild Jan 16 '12 at 17:31
  • Yeah... crashed app on mine. – Jeremy Logan Oct 16 '13 at 22:16
  • 1
    what about launchers/home replacement apps? I'm building one and I want to go to the first screen when the users clicks home – lisovaccaro Jan 08 '15 at 01:45
  • For Launchers use this: @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); /*Do what you want*/ } – Ton Mar 13 '15 at 00:58
  • @lisovaccaro launcher and or home replacements are still not supported by google (src diane hackborn) and therefore you can't prevent the user to click the homebutton. You can still, add your view as a system alert dialog, which will overlay everything. But the home button clicks will go through it. – JacksOnF1re Mar 23 '15 at 18:56
4

Override onUserLeaveHint() in the activity. There will never be any callback to the activity when a new activity comes over it or user presses back press.

Napolean
  • 5,303
  • 2
  • 29
  • 35
4

onUserLeaveHint();

override this activity class method.This will detect the home key click . This method is called right before the activity's onPause() callback.But it will not be called when an activity is interrupted like a in-call activity comes into foreground, apart from that interruptions it will call when user click home key.

@Override
protected void onUserLeaveHint() {
    super.onUserLeaveHint();
    Log.d(TAG, "home key clicked");
}
Basheer Kohli
  • 154
  • 2
  • 11
3

Since API 14 you can use the function onTrimMemory() and check for the flag TRIM_MEMORY_UI_HIDDEN. This will tell you that your Application is going to the background.

So in your custom Application class you can write something like:

override fun onTrimMemory(level: Int) {
    if (level == TRIM_MEMORY_UI_HIDDEN) {
        // Application going to background, do something
    }
}

For an in-depth study of this, I invite you to read this article: http://www.developerphil.com/no-you-can-not-override-the-home-button-but-you-dont-have-to/

Renaud C.
  • 123
  • 8
  • 1
    Good article - useful alternative that probably does what most people need – Dean Wild Mar 13 '19 at 09:59
  • Nice solution. Do you know how to reset the flag when the application returns from the background? For instance if I create a boolean isInBackground, I would want to reset it to once we return from the background. – MikeOscarEcho Jul 31 '19 at 19:52
2

Try to create a counter for each screen. If the user touch HOME, then the counter will be zero.

public void onStart() {
  super.onStart();
  counter++;
}

public void onStop() {
  super.onStop();
  counter--;    
  if (counter == 0) {
      // Do..
  }
}
lalosoft
  • 134
  • 6
  • 3
    If you mean global application counter it will be zero in a moment when one activity is being moved to back stack and another is being moved to top or when top ativity is finished and back stack activity is being moved to top which is the usual place when you want to react on home button press. If you mean activity-wide counter it will be zero any time the activity is not visible (not necessarily caused by home button press). The only solution would be to postpone your reaction using timer to skip this transition but the necessary delay may not be predictable or desirable. – Blackhex Mar 14 '13 at 20:06
2

You might consider a solution by Andreas Shrade in his post on How-To Create a Working Kiosk Mode in Android. It's a bit hacky, but given the reasons that interception of the home button is prevented it has to be ;)

Vito
  • 1,580
  • 1
  • 17
  • 32
1

I had this problem, and since overriding the onKeyDown() method didn't accomplish anything because of the underlying android system didn't call this method, I solved this with overriding onBackPressed(), and I had a boolean value set there to false, because I pressed back, let me show you what I mean in code:

import android.util.Log;
public class HomeButtonActivity extends Activity {
    boolean homePressed = false;
    // override onCreate() here.

    @Override
    public void onBackPressed() {
        homePressed = false; // simply set homePressed to false
    }

    @Overide
    public void onResume() {
        super.onResume();
        homePressed = true; // default: other wise onBackPressed will set it to false
    }

    @Override
    public void onPause() {
        super.onPause();
        if(homePressed) { Log.i("homePressed", "yay"); }
    }

So the reason why this worked is because the only way to navigate outside this activity is by pressing back or home so if back was pressed then i know the cause wasn't home, but otherwise the cause was home, therefore i set the default boolean value for homePressed to be true. However this will only work with a single activity instance in your application because otherwise you have more possibilities to cause the onPause() method to be called.

Moshe Rabaev
  • 1,892
  • 16
  • 31
  • in fact when you go to another activity, the onpause method is called – Fakher Feb 05 '16 at 10:18
  • That's why i explicitly stated that this will only work if your application only incorporates a single activity! – Moshe Rabaev Feb 06 '16 at 23:36
  • And what if multiple unrelated activites are running at the same time, and the user is simply switching between them? Modern Android devices support multi-tasking. With this code, it looks like switching back to your app would set `homePressed` to true, and then switching to another app would think Home was pressed when it really wasn't. – Remy Lebeau Oct 27 '16 at 17:20
1

Recently I was trying to detect the home press button, because I needed it to do the same as the method "onBackPressed()". In order to do this, I had to override the method "onSupportNavigateUp()" like this:

override fun onSupportNavigateUp(): Boolean {
    onBackPressed()
    return true
}

It worked perfectly. =)

1

enter image description here Android Home Key handled by the framework layer you can't able to handle this in the application layer level. Because the home button action is already defined in the below level. But If you are developing your custom ROM, then It might be possible. Google restricted the HOME BUTTON override functions because of security reasons.

Stephan John
  • 331
  • 1
  • 2
  • 11
1

Since you only wish for the root activity to be reshown when the app is launched, maybe you can get this behavior by changing launch modes, etc. in the manifest?

For instance, have you tried applying the android:clearTaskOnLaunch="true" attribute to your launch activity, perhaps in tandem with android:launchMode="singleInstance"?

Tasks and Back Stack is a great resource for fine-tuning this sort of behavior.

Josh
  • 10,618
  • 2
  • 32
  • 36
  • This would seem like the most elegant solution but I have found it to be quite unreliable. After a few open/close/pause cycles the app will start to just resume rather than restart completely – Dean Wild Jan 16 '12 at 16:49
0

It's a bad idea to change the behavior of the home key. This is why Google doesn't allow you to override the home key. I wouldn't mess with the home key generally speaking. You need to give the user a way to get out of your app if it goes off into the weeds for whatever reason.

I'd image any work around will have unwanted side effects.

Andi Jay
  • 5,882
  • 13
  • 51
  • 61
  • 1
    You are absolutely spot on but some clients won't take no for an answer and don't understand why they shouldn't break the guidelines. – Dean Wild Jul 20 '12 at 09:04
  • The problem is that even if you don't want to chage home button behaviour, you occasionally have to react differently on the situation that application is moved to back due to home button press differently than on the situation that you current activity is paused for wathever reason. The same problem is with the fact that Application.onDestroy() cannot be used for poduction builds. Such examples is pausing a game when user hides the appication, stopping background music, etc. – Blackhex Mar 14 '13 at 18:32
0

Jack's answer is perfectly working for click event while longClick is considering is as menu button click.

By the way, if anyone is wondering how to do via kotlin,

class HomeButtonReceiver(private var context: Context,private var listener: OnHomeButtonClickListener) {
    private val mFilter: IntentFilter = IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
    private var mReceiver: InnerReceiver = InnerReceiver()

    fun startWatch() {
        context.registerReceiver(mReceiver, mFilter)
    }

    fun stopWatch() {
        context.unregisterReceiver(mReceiver)
    }

    inner class InnerReceiver: BroadcastReceiver() {
        private val systemDialogReasonKey = "reason"
        private val systemDialogReasonHomeKey = "homekey"
        override fun onReceive(context: Context?, intent: Intent?) {
            val action = intent?.action
            if (action == Intent.ACTION_CLOSE_SYSTEM_DIALOGS) {
                val reason = intent.getStringExtra(systemDialogReasonKey)
                if (reason != null && reason == systemDialogReasonHomeKey) {
                    listener.onHomeButtonClick()
                }
            }
        }
    } 
}
Aditya
  • 3,525
  • 1
  • 31
  • 38
0

SOLVED: This works for BACK, HOME and TASK OVERVIEW

@Override
public void onBackPressed() {
}

@Override
public void onPause() {
    if (isApplicationSentToBackground(MainActivity.this)){
        // Do what you want to do on detecting Home Key being Pressed
        Toast.makeText(this, "You pressed the task button!", Toast.LENGTH_LONG).show();
    }
    super.onPause();
    ActivityManager activityManager = (ActivityManager) getApplicationContext()
            .getSystemService(Context.ACTIVITY_SERVICE);

    activityManager.moveTaskToFront(getTaskId(), 0);
}

@SuppressWarnings("deprecation")
public boolean isApplicationSentToBackground(final Context context) {
    ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
    if (!tasks.isEmpty()) {
        ComponentName topActivity = tasks.get(0).topActivity;
        if (!topActivity.getPackageName().equals(context.getPackageName())) {
            return true;
        }
    }
    return false;
}

And add to manifest: android.permission.REORDER_TASKS

Roy Fagon
  • 61
  • 2
0

An option for your application would be to write a replacement Home Screen using the android.intent.category.HOME Intent. I believe this type of Intent you can see the home button.

More details:

http://developer.android.com/guide/topics/intents/intents-filters.html#imatch

ademar111190
  • 14,215
  • 14
  • 85
  • 114
-1

This works for me. You can override onUserLeaveHint method https://www.tutorialspoint.com/detect-home-button-press-in-android

@Override
    protected void onUserLeaveHint() {
        //
        super.onUserLeaveHint();
    }
Tomasz Kot
  • 302
  • 2
  • 6
  • 1
    This does work only when you have only one activity in your app. If you have multiple activity, this does not work bc when you switch to another activity, this method also triggered. Not good. – Yosidroid Jan 28 '21 at 17:21
  • this also does not work if you trigger gallery intents and the like – rm8x Feb 05 '21 at 21:07
  • this was also suggested as an answer to this question in 2017 – rm8x Feb 05 '21 at 21:11