Detectar o pressionamento do botão home no Android

94

Isso tem me deixado maluco por um tempo.

Existe alguma maneira de detectar com segurança se o botão home foi pressionado em um aplicativo Android?

Caso contrário, existe uma maneira robusta de dizer o que fez com que uma atividade entrasse na pausa? Ou seja, podemos detectar se foi causado pelo início de uma nova atividade ou pressionando voltar / home.

Uma sugestão que vi é substituir onPause () e chamar isFinishing (), mas isso retornará falso ao pressionar o botão home, assim como faria se uma nova atividade estivesse sendo iniciada, portanto, não consegue distinguir entre os dois.

Qualquer ajuda muito apreciada.

** Atualização **: Obrigado @ android -gry por este link: https://nishandroid.blogspot.com/

Substituindo o seguinte método:

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

Em seguida, o seguinte evento SERÁ disparado ao pressionar o botão home:

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

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

Não tenho certeza se há efeitos colaterais com esta linha:

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

Portanto, ao contrário da crença popular, parece que você pode ouvir a tecla home. É preocupante que você retorne false e a chave home não faça nada.

Atualização : Como esperado, existem alguns efeitos colaterais com isso - parece que os vídeos incorporados e mapas do Google não são visíveis com este modo habilitado.

Atualização : supostamente, este hack não funciona mais a partir do Android 4.0

Dean Wild
fonte
Meu problema não era disfarçar entre back e home -key, mas queria finalizar a aplicação em ambos os casos. O que eu fiz usando Activity.onUserLeaveHint().
harism
O único problema é que onUserLeaveHint () também será acionado quando eu iniciar uma atividade a partir dessa atividade, só quero saber se voltar ou home foi pressionado. Obrigado pela sugestão
Dean Wild
Isso é verdade, mas infelizmente, pelo que eu sei, é o único lugar para receber informações sobre o uso da tecla Home. Tornando mais complicado colher falsos-positivos, muitos podem ser reconhecidos facilmente, mas ainda assim tornando a tarefa fácil de soar bastante complicada.
harism
2
@DeanWild: você leu isto: nisha113a5.blogspot.com
Pratik Bhat
2
A constante TYPE_KEYGUARD foi removida de WindowManager.LayoutParams no Android 5.0
theb1uro de

Respostas:

136

O código a seguir funciona para mim :)

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
fonte
3
Seu onHomeLongPressedrealmente parece corresponder à abertura da atividade do sistema "Recentes". No meu telefone, isso é acionado pressionando o botão Recentes ao lado do botão home, de modo que a suposição do seu código sobre ser um pressionamento longo doméstico nem sempre é correta.
Sam
por que não funciona para mim, eu fiz exatamente o mesmo, exceto registrou a transmissão através do manifesto.
Farhan
Registrado na classe de aplicativo, Trabalhando até agora .. +1, eu me pergunto qual é o truque? Quero dizer, de qual caso original estaríamos perdendo ..: ^)
Farhan
1
às vezes intent.getStringExtra (SYSTEM_DIALOG_REASON_KEY); return null. Eu gostaria de saber o que esta acontecendo ??
Fakher de
1
O motivo do final String SYSTEM_DIALOG_REASON_LONG_PRESS = "assist"
toque
49

Esta é uma pergunta antiga, mas pode ajudar alguém.

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

De acordo com a documentação, o método onUserLeaveHint () é chamado quando o usuário clica no botão home OU quando algo interrompe seu aplicativo (como uma chamada telefônica).

Isso funciona para mim .. :)

TODOS
fonte
26
Incorreto !!! Isso funciona quando o botão home é pressionado, mas também funciona quando se ativa a atividade com intenção !!
Nikunj Paradva
Isso sempre será executado antes de onStop (), mesmo se outra atividade vier na parte superior ou o usuário forçosamente sair da atividade ou clicar no botão home ...
Navas pk
7

É impossível detectar e / ou interceptar o botão HOME de um aplicativo Android. Isso é integrado ao sistema para evitar aplicativos maliciosos que não podem ser encerrados.

Jave
fonte
verifique a resposta aceita, é possível. Não testado em muitos dispositivos ainda.
Dean Wild
Sim ... travou o aplicativo no meu.
Jeremy Logan
e quanto aos lançadores / aplicativos de substituição de casa? Estou construindo um e quero ir para a primeira tela quando os usuários
clicarem na página
Para Launchers, use: @Override protected void onNewIntent (Intent intent) {super.onNewIntent (intent); / * Faça o que quiser * /}
Ton
@lisovaccaro launcher e / ou home substitutos ainda não são suportados pelo google (src diane hackborn) e, portanto, você não pode impedir o usuário de clicar no botão home. Você ainda pode adicionar sua visualização como uma caixa de diálogo de alerta do sistema, que irá sobrepor tudo. Mas os cliques do botão home irão passar por ele.
JacksOnF1re
7

Eu precisava iniciar / parar a música de fundo em meu aplicativo quando a primeira atividade abre e fecha ou quando qualquer atividade é pausada pelo botão de início e, em seguida, retomada do gerenciador de tarefas. A reprodução pura parando / retomando Activity.onPause()e Activity.onResume()interrompendo a música por um tempo, então eu tive que escrever o seguinte código:

@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
  }
}

que interrompe a reprodução apenas quando o aplicativo (todas as suas atividades) é fechado ou quando o botão home é pressionado. Infelizmente não consegui mudar a ordem das chamadas do onPause()método da atividade inicial e onResume()da atividade iniciada quando Activity.startActivity()é chamada (ou detecta onPause()que essa atividade está iniciando outra atividade de outra forma) então este caso tem que ser tratado de maneira especial:

private boolean mIsStarting;

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

Outra desvantagem é que isso requer GET_TASKSpermissão adicionada para AndroidManifest.xml:

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

Modificar esse código para que ele reaja apenas ao pressionar o botão home é direto.

Blackhex
fonte
4

Substituir onUserLeaveHint()na atividade. Nunca haverá nenhum retorno de chamada para a atividade quando uma nova atividade ocorrer ou o usuário pressionar a tecla de retorno.

Napoleano
fonte
4
também é chamado ao passar de uma atividade para outra dentro do aplicativo
Amir Uval
3

onUserLeaveHint ();

substituir este método de classe de atividade. Isso detectará o clique da tecla home. Este método é chamado imediatamente antes do retorno de chamada onPause () da atividade. Mas ele não será chamado quando uma atividade for interrompida como uma atividade em chamada vem para o primeiro plano, além das interrupções que ele chamará quando o usuário clicar na tecla home.

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

Tente criar um contador para cada tela. Se o usuário tocar em INÍCIO, o contador será zero.

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

public void onStop() {
  super.onStop();
  counter--;    
  if (counter == 0) {
      // Do..
  }
}
lalosoft
fonte
3
Se você quer dizer o contador de aplicação global, ele será zero no momento em que uma atividade está sendo movida para a pilha posterior e outra para o topo ou quando a atividade superior é concluída e a atividade da pilha traseira está sendo movida para o topo, que é o lugar usual quando você deseja reagir ao pressionar o botão home. Se você quer dizer o contador de toda a atividade, ele será zero sempre que a atividade não estiver visível (não necessariamente causada pelo pressionamento do botão home). A única solução seria adiar sua reação usando um cronômetro para pular essa transição, mas o atraso necessário pode não ser previsível ou desejável.
Blackhex
2

Você pode considerar uma solução de Andreas Shrade em seu post sobre Como criar um modo quiosque funcional no Android . É um pouco hacky, mas devido aos motivos pelos quais a interceptação do botão home é impedida, tem que ser;)

Vito
fonte
1

Eu tive esse problema, e como a substituição do método onKeyDown () não resultou em nada porque o sistema Android subjacente não chamou esse método, resolvi isso substituindo onBackPressed () e tinha um valor booleano definido como falso , porque pressionei de volta, deixe-me mostrar o que quero dizer com código:

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

Então, isso funcionou porque a única maneira de navegar fora desta atividade é pressionando voltar ou home, então, se voltar, então eu sei que a causa não era home, mas caso contrário, a causa era home, portanto, defino o booleano padrão valor para homePressed ser verdadeiro. No entanto, isso só funcionará com uma única instância de atividade em seu aplicativo, caso contrário, você terá mais possibilidades de fazer com que o método onPause () seja chamado.

Moshe Rabaev
fonte
na verdade, quando você vai para outra atividade, o método onpause é chamado
Fakher
É por isso que declarei explicitamente que isso só funcionará se seu aplicativo incorporar apenas uma única atividade!
Moshe Rabaev
E se várias atividades não relacionadas estiverem em execução ao mesmo tempo e o usuário simplesmente alternar entre elas? Dispositivos Android modernos suportam multitarefa. Com este código, parece que voltar para seu aplicativo seria definido homePressedcomo verdadeiro e, em seguida, alternar para outro aplicativo pensaria que Home foi pressionado quando na verdade não estava.
Remy Lebeau
1

Desde a API 14, você pode usar a função onTrimMemory()e verificar o sinalizador TRIM_MEMORY_UI_HIDDEN. Isso informará que seu aplicativo ficará em segundo plano.

Portanto, em sua classe de aplicativo personalizada, você pode escrever algo como:

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

Para um estudo aprofundado sobre isso, convido você a ler este artigo: http://www.developerphil.com/no-you-can-not-override-the-home-button-but-you-dont-have -para/

Renaud C.
fonte
1
Bom artigo - alternativa útil que provavelmente faz o que a maioria das pessoas precisa
Dean Wild
Ótima solução. Você sabe como redefinir o sinalizador quando o aplicativo retorna do segundo plano? Por exemplo, se eu criar um isInBackground booleano, gostaria de redefini-lo assim que retornarmos do plano de fundo.
MikeOscarEcho
0

Como você deseja apenas que a atividade raiz seja mostrada novamente quando o aplicativo for iniciado, talvez você possa obter esse comportamento alterando os modos de inicialização etc.

Por exemplo, você já tentou aplicar o atributo android: clearTaskOnLaunch = "true" à sua atividade de inicialização, talvez em conjunto com android: launchMode = "singleInstance" ?

Tarefas e pilha de retorno são um ótimo recurso para ajustar esse tipo de comportamento.

Josh
fonte
Esta poderia parecer a solução mais elegante, mas descobri que não é confiável. Após alguns ciclos de abrir / fechar / pausar, o aplicativo começará a ser retomado em vez de reiniciar completamente
Dean Wild
0

É uma má ideia alterar o comportamento da tecla home. É por isso que o Google não permite que você substitua a chave home. Eu não mexeria com a chave de casa em geral. Você precisa dar ao usuário uma maneira de sair do seu aplicativo se ele se perder por qualquer motivo.

Imagino que qualquer solução alternativa terá efeitos colaterais indesejados.

Andi Jay
fonte
1
Você acertou em cheio, mas alguns clientes não aceitam não como resposta e não entendem por que não deveriam violar as diretrizes.
Dean Wild
O problema é que, mesmo que você não queira alterar o comportamento do botão home, ocasionalmente terá que reagir de forma diferente na situação em que o aplicativo é movido para trás devido ao pressionamento do botão home de forma diferente da situação em que sua atividade atual está pausada para qualquer coisa razão. O mesmo problema é com o fato de que Application.onDestroy () não pode ser usado para compilações de produção. Esses exemplos são pausar um jogo quando o usuário oculta o aplicativo, parando a música de fundo, etc.
Blackhex
0

Recentemente, estava tentando detectar o botão home, porque precisava que ele fizesse o mesmo que o método " onBackPressed () ". Para fazer isso, tive que substituir o método " onSupportNavigateUp () " assim:

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

Funcionou perfeitamente. =)

Alexandre Cavalheiro Becker
fonte
0

A resposta de Jack está funcionando perfeitamente para o clickevento enquanto longClickestá considerando é um menuclique de botão.

A propósito, se alguém está se perguntando como fazer 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 S.
fonte
0

insira a descrição da imagem aqui Android Home Key tratada pela camada de estrutura, você não pode lidar com isso no nível da camada de aplicativo. Porque a ação do botão home já está definida no nível inferior. Mas se você estiver desenvolvendo sua ROM personalizada, talvez seja possível. O Google restringiu as funções de substituição do BOTÃO INICIAL por motivos de segurança.

Stephan J
fonte