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 e开发者_运维技巧rror, "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.
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...
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;
}
}
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();
}
}
}
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.
精彩评论