是否可以创建一个监听硬件按键按下的 Android 服务?

发布于 2024-09-04 21:21:19 字数 875 浏览 3 评论 0原文

我想运行一个 Android 后台服务,该服务将充当主屏幕或手机休眠时的按键侦听器。这可能吗?

从半相关的在线示例中,我组合了以下服务,但收到错误“onKeyDown 对于类型服务未定义”。这是否意味着如果不重写启动器就无法完成,或者我是否遗漏了一些明显的东西?

public class ServiceName extends Service {

    @Override
    public void onCreate() {
        //Stuff
    }

    public IBinder onBind(Intent intent) {
        //Stuff
        return null;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if(event.getAction() == KeyEvent.ACTION_DOWN) {
        switch(keyCode) {
        case KeyEvent.KEYCODE_A:
            //Stuff
            return true;
        case KeyEvent.KEYCODE_B:
            //Stuff
            return true;

            //etc.
        }
        }

        return super.onKeyDown(keyCode, event);
    }
}

我意识到当你从主屏幕打字时,Android 默认使用搜索栏,但这确实只是为了一个非常特殊的用途。除了我之外,我真的不希望任何人想要这个。例如,我认为使用相机按钮唤醒手机会很好。

I'd like to run an Android background service that will act as a keylistener from the home screen or when the phone is asleep. Is this possible?

From semi-related examples online, I put together the following service, but get the error, "onKeyDown is undefined for the type Service". Does this mean it can't be done without rewriting Launcher, or is there something obvious I'm missing?

public class ServiceName extends Service {

    @Override
    public void onCreate() {
        //Stuff
    }

    public IBinder onBind(Intent intent) {
        //Stuff
        return null;
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if(event.getAction() == KeyEvent.ACTION_DOWN) {
        switch(keyCode) {
        case KeyEvent.KEYCODE_A:
            //Stuff
            return true;
        case KeyEvent.KEYCODE_B:
            //Stuff
            return true;

            //etc.
        }
        }

        return super.onKeyDown(keyCode, event);
    }
}

I realize Android defaults to the search bar when you type from the home screen, but this really is just for a very particular use. I don't really expect anyone but me to want this. I just think it'd be nice, for example, to use the camera button to wake the phone.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

人间不值得 2024-09-11 21:21:19

据我所知,KeyEvents 只能由 Activity 处理,因为它们是用户按键的界面。服务在后台运行,不会对用户输入做出反应。这也是编译器警告“onKeyDown 对于类型 Service 未定义”的原因。服务或其任何超类不实现 KeyEvent.Callback 接口。作为解决方法,您可以在 AndroidManifest.xml 中注册一个 Activity,以对某些系统通知(例如 android.intent.action.SCREEN_ON)做出反应。当按下电源按钮打开屏幕时,您的活动可以启动,初始化某种服务并返回后台。如果这就是你打算做的。请参阅Intent 文档了解可能的操作。

希望有帮助...

As far as I know KeyEvents can only be handled by Activities as they are the interface to the user pressing the keys. Services run in the background and are not intended to react on user input. That's also the reason of your compiler warning "onKeyDown is undefined for the type Service". Service or any of it's Superclasses don't implement the KeyEvent.Callback interface. As a workaround you could register an Activity in your AndroidManifest.xml to react on certain system notifications such as android.intent.action.SCREEN_ON. When the power button is pressed to turn on the screen your activity could be started, initializing a service of some kind and go back to the background. If that's what you intend to do. See Intent docs for possible Actions.

Hope that helped...

傻比既视感 2024-09-11 21:21:19

KeyEvents 需要触发 Activity。因此,无法通过服务检测到硬件按键,因为服务没有主机活动。您可以要求一个 SystemOverlay,并创建一个透明的活动。但这种方法似乎不适用于 API 26+ 设备。

解决此问题的方法是通过 AccessibilityServices 设置观察者。这允许您全局检测硬件按键。

注意:将应用程序启用为辅助功能应用程序可能会导致重大安全问题,用户会对启用此功能持谨慎态度。因此,在您的应用程序对于您的应用程序将处理的数据可以对用户“透明”的情况下,这是可取的。
此方法适用于所有 API 21+ 设备,我尚未在低于此的设备上进行测试,因此它可能有效,也可能无效。

步骤:

  • 使用以下选项创建 XML 文件

     <辅助功能服务
        android:accessibilityFlags="flagRequestFilterKeyEvents"
        android:accessibilityEventTypes="typeAllMask"
        android:accessibilityFeedbackType="feedbackAllMask"
        安卓:notificationTimeout =“100”
        android:canRetrieveWindowContent="true"
        机器人:设置活动=“”
        android:packageNames="你的包名称"
        android:canRequestFilterKeyEvents="true"
      >>
    
  • 在清单中定义您的 AccessibilityService

    
            <意图过滤器>
                <动作 android:name="android.accessibilityservice.AccessibilityService" />
            
            <元数据 android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_layout" />
    
    
  • 创建 AccessibilityService 类

public class AccessibilityKeyDetector extends AccessibilityService {

    private final String TAG = "AccessKeyDetector";

    @Override
    public boolean onKeyEvent(KeyEvent event) {
        Log.d(TAG,"Key pressed via accessibility is: "+event.getKeyCode());
        //This allows the key pressed to function normally after it has been used by your app.
        return super.onKeyEvent(event);
    }


    @Override
    protected void onServiceConnected() {
        Log.i(TAG,"Service connected");

    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {

    }


    @Override
    public void onInterrupt() {

    }
}

  • 创建一个 MainActivity,用于处理此权限请求。
public class MainActivity extends AppCompatActivity {

    private final String TAG = "Test";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkAccessibilityPermission();
    }

    public boolean checkAccessibilityPermission() {
        int accessEnabled=0;
        try {
            accessEnabled = Settings.Secure.getInt(this.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
            e.printStackTrace();
        }
        if (accessEnabled==0) {
            /** if not construct intent to request permission */
            Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            /** request permission via start activity for result */
            startActivity(intent);
            return false;
        } else {
            return true;
        }
    }


    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.d(TAG,"Key pressed");

//this prevents the key from performing the base function. Replace with super.onKeyDown to let it perform it's original function, after being consumed by your app.
        return true;

    }
}

KeyEvents require an Activity to be triggered. Hence, hardware key presses can't be detected via Services,as Services do not have a host activity. You could ask for a SystemOverlay, and create a transparent Activity. But this approach doesn't seem to work on API 26+ devices.

A workaround for this would be set up an observer through AccessibilityServices. This allows you to globally detect hardware key presses.

Note: Enabling an app as an Accessibility App can lead to major security issues, and users would be wary of enabling this. So this would be advisable in situations where your application can be "transparent" to the user with regard to the data your app will be handling.
This method works for all API 21+ devices, I haven't tested on devices below this, so it may or may not work.

Steps:

  • Create an XML file, with the following options

      <accessibility-service
        android:accessibilityFlags="flagRequestFilterKeyEvents"
        android:accessibilityEventTypes="typeAllMask"
        android:accessibilityFeedbackType="feedbackAllMask"
        android:notificationTimeout="100"
        android:canRetrieveWindowContent="true"
        android:settingsActivity=""
        android:packageNames="yourpackagename"
        android:canRequestFilterKeyEvents="true"
      />
    
  • Define your AccessibilityService in the Manifest

    <service android:name=".Services.AccessibilityKeyDetector"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>
            <meta-data android:name="android.accessibilityservice"
                android:resource="@xml/accessibility_layout" />
    </service>
    
  • Create an AccessibilityService Class

public class AccessibilityKeyDetector extends AccessibilityService {

    private final String TAG = "AccessKeyDetector";

    @Override
    public boolean onKeyEvent(KeyEvent event) {
        Log.d(TAG,"Key pressed via accessibility is: "+event.getKeyCode());
        //This allows the key pressed to function normally after it has been used by your app.
        return super.onKeyEvent(event);
    }


    @Override
    protected void onServiceConnected() {
        Log.i(TAG,"Service connected");

    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {

    }


    @Override
    public void onInterrupt() {

    }
}

  • Create a MainActivity, that handles the permission request for this.
public class MainActivity extends AppCompatActivity {

    private final String TAG = "Test";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkAccessibilityPermission();
    }

    public boolean checkAccessibilityPermission() {
        int accessEnabled=0;
        try {
            accessEnabled = Settings.Secure.getInt(this.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
            e.printStackTrace();
        }
        if (accessEnabled==0) {
            /** if not construct intent to request permission */
            Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            /** request permission via start activity for result */
            startActivity(intent);
            return false;
        } else {
            return true;
        }
    }


    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        Log.d(TAG,"Key pressed");

//this prevents the key from performing the base function. Replace with super.onKeyDown to let it perform it's original function, after being consumed by your app.
        return true;

    }
}
迷爱 2024-09-11 21:21:19

这需要 Lollipop (v5.0/API 21) 或更高版本,并且只能检测音量键

它将覆盖音量键操作,因此可能不需要全局使用它。

public class VolumeKeyController {

    private MediaSessionCompat mMediaSession;
    private final Context mContext;

    public VolumeKeyController(Context context) {
        mContext = context;
    }

    private void createMediaSession() {
        mMediaSession = new MediaSessionCompat(mContext, KeyUtil.log);

        mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
                MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
        mMediaSession.setPlaybackState(new Builder()
                .setState(PlaybackStateCompat.STATE_PLAYING, 0, 0)
                .build());
        mMediaSession.setPlaybackToRemote(getVolumeProvider());
        mMediaSession.setActive(true);
    }

    private VolumeProviderCompat getVolumeProvider() {
        final AudioManager audio = mContext.getSystemService(Context.AUDIO_SERVICE);

        int STREAM_TYPE = AudioManager.STREAM_MUSIC;
        int currentVolume = audio.getStreamVolume(STREAM_TYPE);
        int maxVolume = audio.getStreamMaxVolume(STREAM_TYPE);
        final int VOLUME_UP = 1;
        final int VOLUME_DOWN = -1;

        return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
            @Override
            public void onAdjustVolume(int direction) {
                // Up = 1, Down = -1, Release = 0
                // Replace with your action, if you don't want to adjust system volume
                if (direction == VOLUME_UP) {
                    audio.adjustStreamVolume(STREAM_TYPE,
                            AudioManager.ADJUST_RAISE, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                }
                else if (direction == VOLUME_DOWN) {
                    audio.adjustStreamVolume(STREAM_TYPE,
                            AudioManager.ADJUST_LOWER, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                }
                setCurrentVolume(audio.getStreamVolume(STREAM_TYPE));
            }
        };
    }

    // Call when control needed, add a call to constructor if needed immediately
    public void setActive(boolean active) {
        if (mMediaSession != null) {
            mMediaSession.setActive(active);
            return;
        }
        createMediaSession();
    }

    // Call from Service's onDestroy method
    public void destroy() {
        if (mMediaSession != null) {
            mMediaSession.release();
        }
    }
}

This requires Lollipop (v5.0/API 21) or higher, and can only detect volume keys

It will override volume key action, so using it globally may not be desired.

public class VolumeKeyController {

    private MediaSessionCompat mMediaSession;
    private final Context mContext;

    public VolumeKeyController(Context context) {
        mContext = context;
    }

    private void createMediaSession() {
        mMediaSession = new MediaSessionCompat(mContext, KeyUtil.log);

        mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
                MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
        mMediaSession.setPlaybackState(new Builder()
                .setState(PlaybackStateCompat.STATE_PLAYING, 0, 0)
                .build());
        mMediaSession.setPlaybackToRemote(getVolumeProvider());
        mMediaSession.setActive(true);
    }

    private VolumeProviderCompat getVolumeProvider() {
        final AudioManager audio = mContext.getSystemService(Context.AUDIO_SERVICE);

        int STREAM_TYPE = AudioManager.STREAM_MUSIC;
        int currentVolume = audio.getStreamVolume(STREAM_TYPE);
        int maxVolume = audio.getStreamMaxVolume(STREAM_TYPE);
        final int VOLUME_UP = 1;
        final int VOLUME_DOWN = -1;

        return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
            @Override
            public void onAdjustVolume(int direction) {
                // Up = 1, Down = -1, Release = 0
                // Replace with your action, if you don't want to adjust system volume
                if (direction == VOLUME_UP) {
                    audio.adjustStreamVolume(STREAM_TYPE,
                            AudioManager.ADJUST_RAISE, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                }
                else if (direction == VOLUME_DOWN) {
                    audio.adjustStreamVolume(STREAM_TYPE,
                            AudioManager.ADJUST_LOWER, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
                }
                setCurrentVolume(audio.getStreamVolume(STREAM_TYPE));
            }
        };
    }

    // Call when control needed, add a call to constructor if needed immediately
    public void setActive(boolean active) {
        if (mMediaSession != null) {
            mMediaSession.setActive(active);
            return;
        }
        createMediaSession();
    }

    // Call from Service's onDestroy method
    public void destroy() {
        if (mMediaSession != null) {
            mMediaSession.release();
        }
    }
}
壹場煙雨 2024-09-11 21:21:19

虽然无法直接在服务中监听硬件按键,但有时您可以监听这些按键的效果。例如,此答案描述了如何推断音量因媒体音量变化而按下的按键。

While it is not possible to listen for hardware key presses directly in a service, you can sometimes listen for the effects of those key presses. For example, this answer describes how to infer volume key presses from changes in media volume.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文