简体   繁体   中英

How to prevent my Samsung device from preventing my alarm application from working?

Android 6, 7 and 8 have a bug in the calendar provider that makes alarms not to work randomly: https://issuetracker.google.com/issues/64502046

So I developed my own calendar-based alarm application. But it doesn't work properly in my Samsung Galaxy J5 2017 with Android 7. Almost every alarm I set on it works successfully, but there was a single alarm that didn't work and I don't know why.

I suspected from the Samsung's power management settings so I put the application to sleep on purpose in battery settings and set some alarms to see if the power settings were responsible for the problem. But all the alarms I tested worked. So I cannot reproduce the failure. I cannot figure out why there was an alarm that didn't work. It was set properly because I read the logs of the application, but when the time arrived, the alarm receiver didn't get called and I cannot reproduce it. I know that it didn't get called because in the code the first thing I do is to log a message and the message wasn't present in the log. Also, before the alarm that didn't work, the device wasn't rebooted and the application wasn't uninstalled so the alarm must have been still set.

This is the class that sets the alarms:

package bembibre.alarmfix.alarms;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;

import java.util.Calendar;

import bembibre.alarmfix.database.RemindersDbAdapter;
import bembibre.alarmfix.logging.Logger;
import bembibre.alarmfix.utils.GeneralUtils;

/**
 * Created by Max Power on 12/08/2017.
 */

/**
 * Sets alarms in the operating system for the reminders of this application.
 */
public class ReminderManager {

    /**
     * This is the key that identifies a metadata item that is attached to the intent of an alarm of
     * a reminder for tracking it.
     */
    public static final String EXTRA_ALARM_ID = "extra_alarm_id";

    private Context mContext;
    private AlarmManager mAlarmManager;

    public ReminderManager(Context context) {
        mContext = context;
        mAlarmManager =
                (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    }

    /**
     * Part of the code that is responsible for setting an alarm.
     *
     * @param taskId  data base identifier of the reminder.
     * @param alarmId number that helps distinguishing each one of the alarms set for a same reminder.
     * @param when    when.
     */
    public void setReminder(long taskId, long alarmId, Calendar when) throws AlarmException {
        Intent i = new Intent(mContext, OnAlarmReceiver.class);
        i.putExtra(RemindersDbAdapter.KEY_ROWID, taskId);
        i.putExtra(ReminderManager.EXTRA_ALARM_ID, alarmId);
        PendingIntent pi = getReminderPendingIntent(i, taskId);

        try {
            this.setAlarm(pi, when);
            Logger.log("An alarm has been set successfully for the reminder at " + GeneralUtils.format(when) + ". Reminder id: " + taskId);
        } catch (Throwable throwable) {
            Logger.log("The system doesn't let us to set an alarm for the reminder at " + GeneralUtils.format(when), throwable);
            throw new AlarmException();
        }
    }

    /**
     * Unsets the alarm that would trigger for the reminder with the given database identifier.
     * When calling this method, the reminder could have been erased from the database and it
     * wouldn't be a problem. This method is only for unsetting its associated alarm from the
     * system.
     *
     * @param taskId  database identifier of the reminder.
     * @param date    date for logging purposes.
     */
    public void unsetReminder(long taskId, String date) {
        Intent i = new Intent(mContext, OnAlarmReceiver.class);
        PendingIntent pi = getReminderPendingIntent(i, taskId);
        mAlarmManager.cancel(pi);
        Logger.log("An alarm has been unset successfully for the reminder at " + date + ". Reminder id: " + taskId);
    }

    /**
     * Returns the <code>PendingIntent</code> object that must be used for calling this application
     * when a reminder's alarm triggers.
     *
     * @param i the intent to be used.
     * @param taskId reminder database identifier, it distingishes each alarm from the others.
     * @return the <code>PendingIntent</code> object.
     */
    private PendingIntent getReminderPendingIntent(Intent i, long taskId) {
        PendingIntent pi = PendingIntent.getBroadcast(mContext, (int)taskId, i, PendingIntent.FLAG_UPDATE_CURRENT);
        return pi;
    }

    /**
     * Sets the alarm in the operating system.
     *
     * @param operation
     * @param when
     */
    private void setAlarm(PendingIntent operation, Calendar when) throws Throwable {
        /*
         * The alarm must be set differently depending on the OS version. Anyway, we need the
         * pending intent in order to know what was the reminder for which the alarm was fired, so
         * then the correct notification will be shown.
         */
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            // Before Marshmallow, we can do this for setting a reliable alarm.
            mAlarmManager.set(AlarmManager.RTC_WAKEUP, when.getTimeInMillis(), operation);
        } else {
            /*
             * Starting from Marshmallow, it seems like this is the only way for setting a reliable
             * alarm.
             * If we use the "alarm clock" framework, the user will see a icon of an alarm clock.
             * If we use the setExactAndAllowWhileIdle the user will see nothing, but the OS can
             * delay alarms at some sort of situations.
             */
            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, when.getTimeInMillis(), operation);
        }
    }
}

And this is the class that should be called when every alarm goes off:

package bembibre.alarmfix.alarms;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

import bembibre.alarmfix.alarms.intentservices.ReminderService;
import bembibre.alarmfix.database.RemindersDbAdapter;
import bembibre.alarmfix.logging.Logger;

/**
 * Created by Max Power on 12/08/2017.
 */

/**
 * Receives alarms from the OS.
 */
public class OnAlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Logger.log("An alarm has been received right now.");
        long rowid = intent.getExtras().getLong(RemindersDbAdapter.KEY_ROWID);
        long alarmId = intent.getExtras().getLong(ReminderManager.EXTRA_ALARM_ID);
        WakeReminderIntentService.acquireStaticLock(context);
        Intent i = new Intent(context, ReminderService.class);
        i.putExtra(RemindersDbAdapter.KEY_ROWID, rowid);
        i.putExtra(ReminderManager.EXTRA_ALARM_ID, alarmId);
        context.startService(i);
    }
}

The full code is here: https://github.com/maykelbembibre/androidcalendarfix

Well as long as no one is able to find a valid solution to the problem that has been laid out, the only clumsy solution that I have been able to apply by myself is made up of the following:

  • Save every pending alarm in the database and delete it as soon as it gets triggered, removed by the user, the phone is rebooted and so forth. Every time the application is open, check if there is a past saved alarm and warn the user that it wasn't triggered.
  • Remember the user that he should review the power settings of his device, as the power management software created by some smartphone brands is in fact meant to prevent applications from work and consume power unless the user allows them to get enabled.

All that said, I think that after this long, Google should have made the continuous activation of an application to be a permission that the application requests to the user automatically, the same as the other permissions, so that if the user understands that it is an alarm application and allows the permission, the application is able to use alarms that are always reliable regardless of power management settings.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM