I want to listen device power button in an android service. I can’t use key listener since I’m in a service. Currently I’m doing this by registering a broadcast listener with android.intent.action.SCREEN_OFF/android.intent.action.SCREEN_ON filter like below.
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
Receiver mReceiver = new Receiver();
registerReceiver(mReceiver, filter);
Problem
This is not an ideal way to do this since screen status can change without pressing the power button. For an example, when ringing the phone for an in-coming call, it will on the screen. How can I listen exact hardware power button? Or is it not possible?
When device power button press then dispatchKeyEvent method call
/* lock power button */
#Override
public boolean dispatchKeyEvent(#NonNull KeyEvent event)
{
return event.getKeyCode() == KeyEvent.KEYCODE_POWER || blockedKeys.contains(event.getKeyCode()) || super.dispatchKeyEvent(event);
}
Related
I have an Activity (Activity_RingAlarm) that is supposed to be launched as a full-screen intent when an alarm rings. The activity is launched fine, no issue there. I want to give the user an option to dismiss the alarm by pressing the power button. To this effect, I had programmed the Activity to listen to Power button press, as well as the Intent.ACTION_SCREEN_OFF broadcast. The relevant portion of code is shown below:
public class Activity_RingAlarm extends AppCompatActivity implements View.OnClickListener {
private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (Objects.equals(intent.getAction(), Intent.ACTION_SCREEN_OFF)) {
DisplayManager displayManager = (DisplayManager) context.getSystemService(DISPLAY_SERVICE);
for (Display display : displayManager.getDisplays()) {
if (display.getState() == Display.STATE_OFF) {
onPowerButtonPressed();
}
}
}
}
};
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setTurnScreenOn(true);
setShowWhenLocked(true);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ringalarm);
// other codes here
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
registerReceiver(broadcastReceiver, intentFilter);
}
#Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(broadcastReceiver);
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Log.e(getClass().getSimpleName(), "keycode = " + keyCode);
if (keyCode == KeyEvent.KEYCODE_POWER) {
onPowerButtonPressed();
return true;
}
return super.onKeyDown(keyCode, event);
}
}
The issue is, while onKeyDown(int, KeyEvent) responds to volume up and down buttons, it does not respond to the power key. Neither in the emulator, nor in my Samsung Galaxy M31 (both running Android 12 API 31).
I tried putting the above code in the main activity of the app, but even there, the issue is the same.
According to answers posted to this question, it is indeed not possible to capture key events related to the power key, but the ACTION_SCREEN_OFF broadcast should still work. But my app doesn't even get that broadcast. The documentation of the above intent action says that the broadcast has nothing to do with the screen turning off, and is broadcasted when the device becomes non-interactive.
In another question (can't re-locate it at the moment), it was said that dispatchKeyEvent(KeyEvent) should work. I tried that too, but it doesn't.
What can I do in this situation?
What I think happens is the activity is destroyed after you press the power button and the broadcast receiver is unregistered before the screen intents have the chance to arrive. What you could do is change the broadcast receiver to be a top level class and unregister it at a moment that is farther after activity destruction: after a screen intent (ACTION_SCREEN_OFF) is delivered to onReceive, after the alarm is dismissed and/or after a time passes from the activity destruction.
You could also try (if you haven't already) eliminating the window flags from the activity to see if they prevent the intents from being delivered to onReceive().
For debugging consider logging when the receiver is registered/unregistered, all intents delivered to onReceive and adding
Intent.ACTION_USER_PRESENT to the IntentFilter.
I want to know the time left before screen off.
I can easily get the timeout defined by system, but the counter resets with each touch the user do. so I need to reset the counter on my side as well.
I don't want to have an activity in foreground but to use (probably) a broadcastReceiver.
You can't do this but you can detect when screen off and screen on - Use actions:
Intent.ACTION_SCREEN_ON
Intent.ACTION_SCREEN_OFF
But do not put it in AndroidManifest.xml you must register this broadcast, example:
public class ScreenReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent){
String action = intent.getAction();
if(action.equals(Intent.ACTION_SCREEN_ON)){
// screen on
} else {
// screen off
}
}
}
To register broadcast (do not call it in this broadcast (ScreenReceiver) - call it in Service, Activity or Application):
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(new ScreenReceiver(), filter);
I guess the best solution is register broadcast in service because when Activity calls onDestroy() broadcast may be automatically unregistered.
I program in recent years to Android and I wonder something:
How to detect the presence of headphones?
There is a method: isWiredHeadsetOn() but it doesn't work.
I've tried that but it doesn't work:
AudioManager am = (AudioManager)getSystemService(AUDIO_SERVICE);
Log.i("am.isWiredHeadsetOn()", am.isWiredHeadsetOn() + "");
if (am.isWiredHeadsetOn()) {
//code
}
Thank you (and sorry if I made spelling mistakes, I am French)
#Phil answer at https://stackoverflow.com/a/19102661/4758255 is a good implementation to detect the headphone.
Here I'm quoting from his answer. Please upvoted his answer not this!
Upvote the answer: https://stackoverflow.com/a/19102661/4758255
I've had an app in the store for three years that monitors both the wired headset and bluetooth state and nobody has ever complained about battery drain. But that is because I am successfully using a single service and broadcast receiver for detecting events from both. Here's the two key classes:
public class HeadsetStateBroadcastReceiver extends BroadcastReceiver {
public static final String[] HEADPHONE_ACTIONS = {
Intent.ACTION_HEADSET_PLUG,
"android.bluetooth.headset.action.STATE_CHANGED",
"android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"
};
#Override
public void onReceive(final Context context, final Intent intent) {
boolean broadcast = false;
// Wired headset monitoring
if (intent.getAction().equals(HEADPHONE_ACTIONS[0]) {
final int state = intent.getIntExtra("state", 0);
AudioPreferences.setWiredHeadphoneState(context, state > 0);
broadcast = true;
}
// Bluetooth monitoring
// Works up to and including Honeycomb
if (intent.getAction().equals(HEADPHONE_ACTIONS[1])) {
int state = intent.getIntExtra("android.bluetooth.headset.extra.STATE", 0);
AudioPreferences.setBluetoothHeadsetState(context, state == 2);
broadcast = true;
}
// Works for Ice Cream Sandwich
if (intent.getAction().equals(HEADPHONE_ACTIONS[2])) {
int state = intent.getIntExtra("android.bluetooth.profile.extra.STATE", 0);
AudioPreferences.setBluetoothHeadsetState(context, state == 2);
broadcast = true;
}
// Used to inform interested activities that the headset state has changed
if (broadcast) {
LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent("headsetStateChange"));
}
}
}
Here is the service I use to register the broadcast receiver:
public class HeadsetMonitoringService extends Service {
HeadsetStateBroadcastReceiver headsetStateReceiver;
#Override
public void onCreate() {
headsetStateReceiver = new HeadsetStateBroadcastReceiver();
final IntentFilter filter = new IntentFilter();
for (String action: HeadsetStateBroadcastReceiver.HEADPHONE_ACTIONS) {
filter.addAction(action);
}
registerReceiver(headsetStateReceiver, filter);
}
#Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
return START_STICKY;
}
#Override
public void onDestroy() {
unregisterReceiver(headsetStateReceiver);
}
#Override
public IBinder onBind(final Intent intent) {
return null;
}
}
And here is my manifest entry:
<service
android:name=".services.HeadsetMonitoringService"
android:enabled="true"
android:exported="false" >
<intent-filter>
<action android:name="initialiseHeadsetService" />
</intent-filter>
</service>
How it works is as follows:
I use an on boot broadcast receiver to send a start service message to the HeadsetMonitoringService (you don't have to do it this way, you could just do this when your application starts instead). The HeadsetMonitoringService in turn registers an instance of a broadcast listener that listens to all the headset events I am interested in - they are held in the HEADPHONE_ACTIONS array. Because the service is sticky it hangs around - and therefore so does the broadcast listener. But because both the service and the broadcast listener are event driven they do not consume any power until a headset state change occurs. Additionally, because the service is sticky, it will be restarted by the OS if it dies unexpectedly.
Whenever I receive a headset state change event I also fire a local broadcast so that interested activities can check the new state and take action if required.
For completeness, I should point out that I use another class (not shown here), AudioPreferences, to store as preferences both the Bluetooth and wired headset state, which can then be accessed whenever I need to know the headset state.
Your application will need the android.permission.BLUETOOTH permission if you are interested in the state of a Bluetooth headset. If not, just take out the Bluetooth related actions from the HEADPHONE_ACTIONS array and delete the associated if blocks from the onReceive method.
If you are OK with Marshmallow and up the AudioDeviceCallback might be what you are looking for. It works with an AudioManager and tells you when anything connects and disconnects.
AudioManager.isWiredHeadsetOn() appeared to be the right thing to do. According to the Android developer doc :
Checks whether a wired headset is connected or not.
This is not a valid indication that audio playback is actually over the wired headset as audio routing depends on other conditions.
Returns
true if a wired headset is connected. false if otherwise
But :
you have to add the associated permission to your manifest (MODIFY_AUDIO_SETTINGS)
according to this post, it doesn't work well with Bluetooth headset.
How can I check from a Service if the KeyGuard (Lockscreen) is visible? I want to support the original and custom Lockscreens.
The screen locks only when the device turns the screen off.
You should extend BroadcastReceiver and implement onReceive, like this:
public class YourBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equalsIgnoreCase(intent.getAction())) {
//screen has been switched off!
}
}
}
Then you just have to register it and you'll start receiving events when the screen is switched off:
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
appBroadcastReceiver = new AppBroadcastReceiver(yourActivity);
registerReceiver(appBroadcastReceiver, filter);
There is an edge case where users have their device set to lock n seconds after the screen goes off, you might want to add a check in your broadcast receiver for the ACTION_SCREEN_ON and check the time between them.
In my application I need to know when device is locked (on HTC's it looks like short press on "power" button). So the question is: which event is triggered when device is locked? Or device is going to sleep?
You should extend BroadcastReceiver and implement onReceive, like this:
public class YourBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_SCREEN_OFF.equalsIgnoreCase(intent.getAction())) {
//screen has been switched off!
}
}
}
Then you just have to register it and you'll start receiving events when the screen is switched off:
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
appBroadcastReceiver = new AppBroadcastReceiver(yourActivity);
registerReceiver(appBroadcastReceiver, filter);
There is a better way:
KeyguardManager myKM = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
if( myKM.inKeyguardRestrictedInputMode()) {
//it is locked
} else {
//it is not locked
}
In addition to the above answer, in-case you want to trigger some action when your app is at the foreground:
You could use the event called onResume() to trigger your own function when your app takes the spotlight from a previously resting state, i.e, if your app was at the background(paused/minimized...)
protected void onResume()
{
super.onResume();
//call user-defined function here
}