Android 8.0 Oreo AlarmManager with broadcast receiver and implicit broadcast ban - android

I have critical reminders that are set via the Alarm Manager (It should function the same way as an alarm clock application).
Previously I had the following in my Android Manifest:
<receiver android:name="com.example.app.AlarmReceiver" >
<intent-filter>
<action android:name="${packageName}.alarm.action.trigger"/>
</intent-filter>
</receiver>
The broadcast receiver:
public class AlarmReceiver extends BroadcastReceiver {
#Override public void onReceive(
final Context context,
final Intent intent) {
// WAKE LOCK
// BUILD NOTIFICATION etc...
}
}
How the alarm is set:
final PendingIntent operation = PendingIntent.getBroadcast(
mContext,
requestCode,
intent,
PendingIntent.FLAG_CANCEL_CURRENT);
if (PlatformUtils.hasMarshmallow()) {
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, operation);
} else {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, operation);
}
}
With Android 8.0 I can no longer use an implicit broadcast as defined in the Manifest. That's fine, the alternative given is to register it manually like so:
final BroadcastReceiver receiver = new AlarmReceiver();
final IntentFilter intentFilter = new IntentFilter(ALARM_RECEIVER_INTENT_TRIGGER);
context.registerReceiver(receiver, intentFilter);
This does not seem logical to me.
The alarm receiver will be tied to the lifetime of the context. This causes an issue when say the application is killed due to memory pressure or when the device is restarted. I need my alarms to fire every time as they are critical for the health of the user.
Even if I listen to "android.intent.action.BOOT_COMPLETED" and register my alarm receiver the app is killed shortly after and no alarm is fired. I also don't see my alarm via
adb shell dumpsys alarm
How do I create a custom broadcast receiver that receives an implicit broadcast to fire an alarm while targeting Android O (8.0)? Can someone enlighten me with a code example or link to documentation. How does Timely or any other alarm clock app function while targeting O?

Revise your code slightly to make the broadcast explicit rather than implicit and you'll be fine (assuming this is an Activity reference or some other Context):
Intent intent = new Intent(ALARM_RECEIVER_INTENT_TRIGGER);
intent.setClass(this, AlarmReceiver.class);
The restriction in Oreo is on implicit broadcast Intent registration, which is to say it you are sending it broadcasts will only action, category, or data specified. You make it an explicit broadcast by specifying the class which is to receive the broadcast.

If you guys are used to check if the alarm has already been registered don't forget to do the same on this verification:
public boolean isAlarmBroadcastRegistered(Context context, String action, Class clazz) {
Intent intent = new Intent(action);
intent.setClass(context, clazz);
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_NO_CREATE) != null;
}

Related

AlarmClock intent says "No apps can perform this action."

I am trying to set an alarm in the alarm clock with an intent. I am using an Android One Phone (which has the unmodified OS) and have the Clock app installed (which came pre-installed) which allows setting alarm. Setting an alarm has worked in the past when I had used an AlarmManager and PendingIntent when I had to set the alarm in the background. That shows that my Clock app can respond to AlarmClock intents. But now when I am trying to send an intent from the foreground of my app, it says:
No apps can perform this action.
This is not from the stack trace, but a popup which is shown to users to choose which Clock app to choose to set the alarm (or which app to use in general for an intent)
Here's the Activity's onCreate() code where I am calling it:
public class MainActivity extends AppCompatActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
Utils.setAlarm(this, Utils.getLDT(epochTime).plusHours(8));
}
}
Here's the Utils#setAlarm function that sends the intent:
public class Utils {
public static void setAlarm(Context context, LocalDateTime alarmTimeDT) {
Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM);
intent.putExtra(AlarmClock.EXTRA_SKIP_UI, true);
intent.putExtra(AlarmClock.EXTRA_HOUR, alarmTimeDT.getHour());
intent.putExtra(AlarmClock.EXTRA_MINUTES, alarmTimeDT.getMinute());
intent.putExtra(AlarmClock.EXTRA_MESSAGE, "Good Morning");
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
...
}
This is the additional code that was used in the past for the same device and same Clock app. This is for cancelling an already set alarm, but the code used to set the old alarm was similar except for the cancelling part:
//cancel old alarm
AlarmManager alarmMgr = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
PendingIntent alarmIntent = PendingIntent.getActivity(
this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
if (alarmIntent != null) {
alarmMgr.cancel(alarmIntent);
} else {
Log.i(TAG, "intent is null");
}
What am I doing wrong? Any help appreciated.
The below comments is just questioning and asking for more clarification on the question, which were done as asked for. So you may skip reading them
No apps can perform this action.
This happens when you don't have the required permission to set the alarm.
From AlarmClock reference:
Applications that wish to receive the ACTION_SET_ALARM and ACTION_SET_TIMER Intents should create an activity to handle the Intent that requires the permission com.android.alarm.permission.SET_ALARM.
Request the SET_ALARM permission:
<mainfest
...
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
</manifest>

Broadcast by Notification Action not handled in BroadcastReceiver inside Service

I am trying to build a notification while a music playback service is running and use the notification to interact with the service (play, pause, stop) using the Broadcast mechanism.
(I know there is also the possibility to use PendingIntent.getService() as an action button in the notification, but I don't like this idea, because this would trigger the onStartCommand() of the service and I need to parse and analyze the Intent object to take action, which seems not as clean as the BroadcastReceiver approach, described below).
Let's illustrate what we have so far with some (truncated) code.
We are creating a Notification object inside the service lifecycle, add an action button, and showing the notification using startForeground().
...
Intent i = new Intent(getBaseContext(), PlayerService.class);
PendingIntent piStop = PendingIntent.getBroadcast(getBaseContext(), 1, i, PendingIntent.FLAG_ONE_SHOT);
NotificationCompat.Action actionStopPlayback = new NotificationCompat.Action(R.drawable.ic_stop_white_36dp, "Stop playback", piStop);
notification.addAction(actionStopPlayback);
...
Then we are registering a BroadcastReceiver inside the onCreate() of the service (and unregistering it in onDestroy of course; this is a more simplified example).
IntentFilter intentFilter = new IntentFilter();
registerReceiver(new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.d(getClass().toString(), "Broadcast received");
}
}, intentFilter);
And the final result is that the onReceive() of the receiver is never called. The service is a continuous one and is active when the Notification action sends the broadcast. Since I have no way of debugging broadcasts due to their nature, I'm kind of blocked here.
You're creating this explicit Intent for the PendingIntent:
Intent i = new Intent(getBaseContext(), PlayerService.class);
This won't work for a couple of reasons. Explicit Intents - those created for a specific target class - do not work with dynamically registered Receiver instances. Also, this is targeting the wrong class. A broadcast Intent with a Service class target will just fail outright. A getBroadcast() PendingIntent would need a BroadcastReceiver class as the target.
With your current setup - the dynamically registered Receiver instance - you'll need to use an implicit Intent; i.e., an Intent with an action String, rather than a target class. For example:
Intent i = new Intent("com.hasmobi.action.STOP_PLAYBACK");
You would then use that action String for the IntentFilter you're using to register the Receiver.
IntentFilter intentFilter = new IntentFilter("com.hasmobi.action.STOP_PLAYBACK");
Do note that an IntentFilter can have multiple actions, so you can register a single Receiver to handle several different actions.
Alternatively, you could stick with using an explicit Intent, and statically register a BroadcastReceiver class in the manifest. For example:
public class NotificationReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
...
}
}
In the manifest:
<receiver android:name=".NotificationReceiver" />
Then your Intent would be similar to:
Intent i = new Intent(PlayerService.this, NotificationReceiver.class);
However, this would require an additional step, as you would then need to somehow pass the broadcast info from NotificationReceiver to the Service; e.g., with an event bus, LocalBroadcastManager, etc.

AlarmManager alarm start when system time is changed by user?

i am using AlarmManager class for setting Alarms it is working fine.
But if i set alarm like 9pm and current time is 8pm and i changed the system time to 10pm
then alarm 9pm alarm start automatically. so to solve this issue
i have searched so much but did not found any good answer
Please help
here is my code for alarm setting
final int id = (int) System.currentTimeMillis();
Intent intent = new Intent(this, AlarmReceiver.class);
intent.putExtra("requestCode", id);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 2*60*1000, pendingIntent);
One of the options is to store all set alarms in database, then create a BroadcastReceiver which will listen for ACTION_TIME_CHANGE action. When user changes time it will be triggered. Then create a IntentService which will be responsible for resetting alarms. In this service class:
Read db and identify all passed alarms.
Cancel passed alarms
Set alarms for next day
Your code may look like as below:
In your Manifest:
<uses-permission android:name="android.permission.ACTION_TIME_CHANGE"/>
and below activities:
<receiver android:name=".TimeChangedReceiver" android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.TIME_SET" />
</intent-filter>
</receiver>
<service android:name=".RestartAlarmsService"/>
Create class "TimeChangedReceiver" inside of which:
public class TimeChangedReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if("android.intent.action.TIME_SET".equals(intent.getAction())) {
Intent i = new Intent(context, RestartAlarmsService.class);
ComponentName service = context.startService(i);
}
}
}
Create "RestartAlarmsService" class inside of which:
public class RestartAlarmsService extends IntentService {
public RestartAlarmsService() {
super("RestartAlarmsService");
}
#Override
protected void onHandleIntent(Intent intent) {
// read db here
// then cancel passed alarms
// reset them to next day
}
}
You can find many tutorials on how to use Databases and implement it in your code. Hope my answer is somehow helpful.
yes it will give you broadcast, as your pending intent object is still attached to that event while you change time that is greater than you alarm firing time.
solution- validate your condition while you receive broadcast from alarm manager

Alarm is killed when OS kills app

My alarm is killed when OS kills the app. I thought that was one of the points of an Alarm, that it would keep running even though OS killed the app? I check the life of the Alarm using the "./adb shell dumpsys alarm" command, and every time OS kills my app, the Alarm is also gone. How I start my Alarm:
public static void startLocationAlarm(Context context){
if(ActivityLifecycleHandler.isApplicationInForeground()) {
return; // If App is in foreground do not start alarm!
}
String alarm = Context.ALARM_SERVICE;
AlarmManager am = ( AlarmManager ) context.getSystemService( alarm );
Intent intent = new Intent(locationBroadcastAction);
PendingIntent pi = PendingIntent.getBroadcast( context.getApplicationContext(), 0, intent, 0 );
int type = AlarmManager.ELAPSED_REALTIME_WAKEUP;
long interval = ONE_MINUTE;
long triggerTime = SystemClock.elapsedRealtime() + interval;
am.setRepeating(type, triggerTime, ONE_MINUTE, pi );
}
To add some more context, I am trying do some location operation in a service (not IntentService) in background. Here is my receiver. Used Wakeful because I did not want the service to be killed before it was done.
public class LocationBroadcastReceiver extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent ) {
Intent myIntent = new Intent( context, LocationServiceAlarmOwnGoogleClient.class );
//context.startW( myIntent );
LocationBroadcastReceiver.startWakefulService(context, myIntent);
}
}
For some more information: I cancel the alarm in OnStart method of several activities that the user can return to after having it in the background. I do not know if that can cause this weird behaviour? Here is my cancel method:
public static void stopLocationAlarm(Context context){
Intent intent = new Intent(locationBroadcastAction);
PendingIntent sender = PendingIntent.getBroadcast(context.getApplicationContext(), 0, intent, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(context.ALARM_SERVICE);
alarmManager.cancel(sender);
}
You can add service which listens to the phone's turning on callback.
add this permission into the manifest
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
and register reciever
<receiver android:name=".util.notification.local.MyBootCompletedService">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
public class MyBootCompletedService extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
AlarmReceiver.startLocalNotificationService(context);
}
}
The error that caused the Alarm to be canceled had actually nothing to do with the code, but had to do with special battery settings on Huawei devices. If your app is not set as "protected" in "protected apps", the system will cancel your alarm when it kills the app. Adding your app to "protected apps" will solve this problem. Same goes for Xiaomi devices. Have to add them to "Protected apps", then the Alarm will work as intended. Thank you #CommonsWare for leading me to the solution.

delay in alarmmanager .. when two alarms exists simultaneously

I have scheduled multiple alarm managers to send intents at different times.
I took care to pass unique ID, context and extras to both the pending intents.
below func handles the alarm calls.
.
public void handle(int duration, int id){
Intent intent = new Intent("package.SET");
intent.putExtra ("package.id", Id);
AlarmManager amg = (AlarmManager)Context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pis = PendingIntent.getBroadcast(Context,Id, intent, FLAG_ONE_SHOT);
amg.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + time, pis); }
there are 2 functions calling the alarm
public void callalarm(int time){
handle(time, UNIQUE_ID1);}
and
public void callalarm2(int time){
handle(time, UNIQUE_ID2);}
I took care that unique ID1 and Unique_ID2 are different.
The broadcast receiver handles the alarm and executes another code.
Is there a possibility that Callalarm1 and callalarm2 interfere with eachother .
I have registered the receiver using registerReceiver function and not in android manifest file.
IntentFilter ARFilter = new IntentFilter();
ARFilter.addAction("package.SET");
context.registerReceiver(AR, ARFilter);
In the AR that extends broadcast receiver, i use the id to define the action.
public BroadcastReceiver AR= new BroadcastReceiver()
{ public void onReceive(Context context, Intent intent)
{ // i do some stuff here which is confidential
}}
The problem is that, I get a delay in the alarms. Is there a reason why there is a delay ?
As of What I know, The Alarm MAnager cannot fire at right times when the task burden on the processor is heavy.
In other words, the broadcast receiver does not receive intent on time since the alarm manager adds the broadcast to the queue which is sent with some delay..
Precise information is helpful.

Categories

Resources