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