检测电源按钮长按

22

我一直在Stackoverflow上阅读一些帖子,但没有找到一个好的解决方案,我想知道是否可以检测用户长按电源按钮试图关闭设备时,能否检测到该事件,并允许或不允许显示出现的对话框(重新启动、关机等)。

我已经尝试过这个:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
        Toast.makeText(MainActivity.this, event.getKeyCode(), Toast.LENGTH_SHORT).show();
    return true;
}

但是它没有显示出来,而且它应该作为一个服务运行,我的意思是应用程序可以或不可以打开以显示那个toast。

编辑

这就是我如何使用onCloseSystemDialog

//home or recent button
public void onCloseSystemDialogs(String reason) {
    if ("globalactions".equals(reason)) {
        Toast.makeText(PowerButtonService.this, "yaps", Toast.LENGTH_SHORT).show();
        Intent i= new Intent(getBaseContext(), Demo.class);
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        getApplication().startActivity(i);
    //} else if ("homekey".equals(reason)) {
        //home key pressed
    //} else if ("recentapss".equals(reason)) {
        // recent apps button clicked
    }
}

它的功能正常,但只有在设备解锁时才能显示,当设备锁定时不显示任何内容。

此外我正在尝试弄清楚如何在用户点击电源按钮时删除对话框。我尝试了这个:

getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);

但是如果我想再次显示它,我该怎么做?


尝试这个:http://stackoverflow.com/a/23559755/5612089 - Vishwesh Jainkuniya
我不是在寻找屏幕开/关,我只是想检测用户按下电源按钮以选择重新启动/关机/等等... - Skizo-ozᴉʞS ツ
当用户按下电源按钮时开始计数器,如果它等于长按时间,则执行您要执行的操作。 - Vishwesh Jainkuniya
这篇文章与你的问题有关:https://dev59.com/TXA65IYBdhLWcg3wqQY8 - محمدرضا ش
尝试过了,这个答案不起作用。 - Skizo-ozᴉʞS ツ
@Skizo-ozᴉʞS 请尝试我的答案,让我Ping一下... 如果有帮助,我们再进行下一步操作,你想要什么... - Arjun saini
12个回答

23

我将分享我的方法来达成你想要实现的目标。

基本上,它所做的是

  1. Asking for system permission to draw overlay (This is not a normal or vulnerable permission). This is not a user permission, so You should really know, what you are doing, by asking for it.

    public class MainActivity extends AppCompatActivity {
    
        public final static int REQUEST_CODE = 10101;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if (checkDrawOverlayPermission()) {
                startService(new Intent(this, PowerButtonService.class));
            }
        }
    
        public boolean checkDrawOverlayPermission() {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                return true;
            }
            if (!Settings.canDrawOverlays(this)) {
                /** if not construct intent to request permission */
                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            /** request permission via start activity for result */
                startActivityForResult(intent, REQUEST_CODE);
                return false;
            } else {
                return true;
            }
        }
    
        @Override
        @TargetApi(Build.VERSION_CODES.M)
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (requestCode == REQUEST_CODE) {
                if (Settings.canDrawOverlays(this)) {
                    startService(new Intent(this, PowerButtonService.class));
                }
            }
        }
    }
    
  2. Starting a service and adds a special view to WindowManager

  3. Waiting for an action inside View's onCloseSystemDialogs method.

    public class PowerButtonService extends Service {
    
        public PowerButtonService() {
    
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            LinearLayout mLinear = new LinearLayout(getApplicationContext()) {
    
                //home or recent button
                public void onCloseSystemDialogs(String reason) {
                    if ("globalactions".equals(reason)) {
                        Log.i("Key", "Long press on power button");
                    } else if ("homekey".equals(reason)) {
                        //home key pressed
                    } else if ("recentapps".equals(reason)) {
                        // recent apps button clicked
                    }
                }
    
                @Override
                public boolean dispatchKeyEvent(KeyEvent event) {
                    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
                        || event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP
                        || event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN
                        || event.getKeyCode() == KeyEvent.KEYCODE_CAMERA
                        || event.getKeyCode() == KeyEvent.KEYCODE_POWER) {
                        Log.i("Key", "keycode " + event.getKeyCode());
                    }
                    return super.dispatchKeyEvent(event);
                }
            };
    
            mLinear.setFocusable(true);
    
            View mView = LayoutInflater.from(this).inflate(R.layout.service_layout, mLinear);
            WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
    
            //params
           final WindowManager.LayoutParams params;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    
        params = new WindowManager.LayoutParams(
                100,
                100,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
                PixelFormat.TRANSLUCENT);
    } else {
        params = new WindowManager.LayoutParams(
                100,
                100,
                WindowManager.LayoutParams.TYPE_PHONE,
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_FULLSCREEN
                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                        | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
                PixelFormat.TRANSLUCENT);
    }
            params.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
            wm.addView(mView, params);
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
    

清单:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="powerbuttonpress">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <service
            android:name=".PowerButtonService"
            android:enabled="true"
            android:exported="true">
        </service>

    </application>

</manifest>

service_layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

</LinearLayout>

其中之一可以在活动之外工作。 - Robin Dijkhof
1
虽然这对我来说有效,但只要系统覆盖层处于活动状态(始终如此),它会导致软键盘无法打开,因此您实际上无法输入任何内容。将TYPE_SYSTEM_ALERT切换为TYPE_SYSTEM_OVERLAY解决了我的问题。 - Syzygy
1
似乎这在Android P(Beta)上不再起作用。当长按电源按钮时,不会调用onCloseSystemDialogs()。 有没有什么解决方法? - drk
1
如果您使用ProGuard,请确保在onCloseSystemDialogs方法中添加@Keep注释。尽管Android P仍然是一个问题。 - drk
2
我在Pixel 2的Android P最新版本中找到了一个解决方法,请参见以下内容。目前还不确定它是否非常通用。 - D2TheC
显示剩余15条评论

6

跟随这个

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

当您想要捕捉该事件时,请执行以下操作:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_POWER) {
        // this is method which detect press even of button
        event.startTracking(); // Needed to track long presses
        return true;
    }
    return super.onKeyDown(keyCode, event);
}

@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_POWER) {
        // Here we can detect long press of power button
        return true;
    }
    return super.onKeyLongPress(keyCode, event);
}

可以通过广播接收器来实现。请参见此答案

我想在用户没有打开应用程序时检测到它。 - Skizo-ozᴉʞS ツ
然后它会打开电源选项,你需要吗?@Skizo-ozᴉʞS - TapanHP
我想在显示这条消息后显示一个对话框,根据用户的点击决定是否显示该消息,你明白我的意思吗? - Skizo-ozᴉʞS ツ
2
无法检测电源按钮。 - Skizo-ozᴉʞS ツ

6

我看到很多答案都试图钩入电源按钮。然而,监听关机事件会更容易吧?我不确定这是否符合您的需求,但似乎是这样。

只需向以下操作注册Broadcastreceiver即可:

action(s):

<receiver android:name=".ShutdownReceiver">
    <intent-filter>
        <action android:name="android.intent.action.ACTION_SHUTDOWN" />
        <action android:name="android.intent.action.QUICKBOOT_POWEROFF" />
    </intent-filter>
</receiver>

不要忘记添加以下权限:

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


(注:此权限用于控制设备的电源管理。)

嗯,但是我能在用户关闭设备之前做一些事情吗? - Skizo-ozᴉʞS ツ
可能是一些不太长的东西。 - Robin Dijkhof
是的,在关机之前你可以做一些事情,但请记住你只有几秒钟的时间。 - Robin Dijkhof
在关机之前,您可以做一些事情,但我建议不要调用服务器或其他任何东西。无论您想做什么,请确保它很快,例如将数据保存到数据库或共享首选项中。然后侦听重新启动广播,并在此之后执行您想要的任何操作。 - ᴛʜᴇᴘᴀᴛᴇʟ
那不是我需要的,我只想在显示关闭选项之前显示一个对话框。 - Skizo-ozᴉʞS ツ

5

长按问题解决方案...

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class demo extends Activity {


    private final List<Integer> blockedKeys = new ArrayList<>(Arrays.asList(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER));


    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
        setContentView(R.layout.demo);


    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (!hasFocus) {
            // Close every kind of system dialog
            Intent closeDialog = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
            sendBroadcast(closeDialog);
            Toast.makeText(demo.this, "Your LongPress Power Button", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onBackPressed() {
        // nothing to do here
        // … really
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (blockedKeys.contains(event.getKeyCode())) {


            return true;
        } else {

            return super.dispatchKeyEvent(event);
        }
    }
}

长按电源按钮将显示测试用的 toast...

输入图像描述


我想在服务中实现 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);,这是可能的吗? - Skizo-ozᴉʞS ツ
请参考Terrence的回答,了解如何在服务上运行:https://dev59.com/9lvUa4cB1Zd3GeqPvJcP - Arjun saini
事情是,有没有办法在设备锁定时显示对话框?我的意思是,我知道如何在设备解锁时显示toast,但问题是,在设备锁定时是否有任何方法可以显示它? - Skizo-ozᴉʞS ツ
如果我不想显示按下电源按钮时出现的选项,我需要调用这个吗?FLAG_DISMISS_KEYGUARD - Skizo-ozᴉʞS ツ

5
这个简单的解决方案可行:
    @Override
public boolean dispatchKeyEvent(KeyEvent event) {
    int keyPressed = event.getKeyCode();
    if(keyPressed==KeyEvent.KEYCODE_POWER){
        Log.d("###","Power button long click");
        Toast.makeText(MainActivity.this, "Clicked: "+keyPressed, Toast.LENGTH_SHORT).show();
        return true;}
    else
        return super.dispatchKeyEvent(event);
}

如果您想要停止系统弹窗,请使用下方其中一个答案中指定的onWindowFocusChanged方法。

输出将会显示如下所示带有 "Clicked:26" 的提示信息:enter image description here


4

R. Zagórski上面提到的方法长期以来对我非常有效,但从某个Android 9版本开始,onCloseSystemDialogs方法停止接收“globalactions”。

作为解决方法,我的应用程序已经在使用Accessibility。在AccessibilityService内部,您可以在onAccessibilityEvent中添加此代码。

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    Log.i(TAG,"Accessibilityevent");
    if (event == null || event.getPackageName()==null||event.getClassName()==null)
        return;


    if (event.getClassName().toString().contains("globalactions")){
        //TRIGGER YOUR CODE HERE

    }
        ....

请注意,当我在“TRIGGER YOUR CODE HERE”处放置注释时,实际上只是调用了先前在onCloseSystemDialogs中的相同代码。
请注意,到目前为止,这仅在运行最新Android的Pixel 2上进行了测试。我没有任何其他运行足够新版本的手机,因此无法测试解决方案是否足够通用。

1
请问您能否发布您的可访问性服务配置?您使用了哪些标志和其他参数来接收“globalactions”回调? - drk
1
如果有人遇到这个问题,那么默认情况下,如果您不指定事件类型,那么您将看不到globalactions事件。 以下似乎是最简单的辅助功能服务配置,以具有globalactions回调:<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeWindowStateChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault" android:packageNames="com.android.systemui" android:notificationTimeout="100" /> - drk
@drk无法工作。 - Vlad

3

请尝试以下操作:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_POWER) {
        Intent i = new Intent(this, ActivitySetupMenu.class);
        startActivity(i);
        return true;
    }

    return super.dispatchKeyEvent(event);
}

2

根据Skizo-ozᴉʞS的问题,我理解你实际上想要防止电源按钮的行为,但这是无法做到的。就像在安卓系统中按下主页按钮一样,有些事件是无法被阻止的,以避免人们使用无法退出的应用程序。


1
你可以通过重写onKeyDown方法来捕获电源按钮事件。
@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_POWER) {
            Log.e("key","long");
            /*//disable the system dialog
            Intent closeDialog = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
            sendBroadcast(closeDialog);*/
            Toast.makeText(getApplicationContext(),"Power",Toast.LENGTH_SHORT).show();
        }
        return super.onKeyDown(keyCode, event);
    }

据我所知,我们无法在外部活动(如后台服务)中执行此操作。


1
The accepted answer works flawlessly till Android Oreo but not in Android P. To make it work in android P you'll need to use Accessibility Service.
Service Class.
import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;

import com.bm.yucasa.Utils.LocationSetup;

import static com.bm.yucasa.controller.AppController.TAG;

public class PowerServiceTwo extends AccessibilityService {
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.i(TAG,"Accessibilityevent");
        if (event == null || event.getPackageName()==null||event.getClassName()==null)
            return;


        if (event.getClassName().toString().contains("globalactions")){
            LocationSetup locationSetup = 
LocationSetup.getInstance(PowerServiceTwo.this);
            if (locationSetup.isConnected) {
                locationSetup.startOnDemandLocationUpdates(false, "0");
            } else {

                //Toast.makeText(getApplicationContext(), "please check gps", Toast.LENGTH_SHORT).show();
            }

        }
    }

    @Override
    public void onInterrupt() {

    }
}

由于无法使用startService()方法启动辅助功能服务,因此以下是如何启用它的方法。

Intent openSettings = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
openSettings.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(openSettings);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接