* The delayed notification controller can also shown on demand and promoted to a regular notification controller to update the message etc.
+ *
+ * Do not call this method on API > 31
*/
public static DelayedNotificationController startForegroundTaskDelayed(@NonNull Context context, @NonNull String task, long delayMillis, @DrawableRes int iconRes) {
- return DelayedNotificationController.create(delayMillis, () -> startForegroundTask(context, task, DEFAULTS.channelId, iconRes));
+ Preconditions.checkArgument(Build.VERSION.SDK_INT < 31);
+
+ return DelayedNotificationController.create(delayMillis, () -> {
+ try {
+ return startForegroundTask(context, task, DEFAULTS.channelId, iconRes);
+ } catch (UnableToStartException e) {
+ Log.w(TAG, "This should not happen on API < 31", e);
+ throw new AssertionError(e.getCause());
+ }
+ });
}
- public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task) {
+ public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task) throws UnableToStartException {
return startForegroundTask(context, task, DEFAULTS.channelId);
}
- public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId) {
+ public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId)
+ throws UnableToStartException
+ {
return startForegroundTask(context, task, channelId, DEFAULTS.iconRes);
}
- public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId, @DrawableRes int iconRes) {
+ public static NotificationController startForegroundTask(
+ @NonNull Context context,
+ @NonNull String task,
+ @NonNull String channelId,
+ @DrawableRes int iconRes)
+ throws UnableToStartException
+ {
final int id = NEXT_ID.getAndIncrement();
Intent intent = new Intent(context, GenericForegroundService.class);
@@ -150,7 +172,17 @@ public final class GenericForegroundService extends Service {
intent.putExtra(EXTRA_ID, id);
Log.i(TAG, String.format(Locale.US, "Starting foreground service (%s) id=%d", task, id));
- ContextCompat.startForegroundService(context, intent);
+
+ if (Build.VERSION.SDK_INT < 31) {
+ ContextCompat.startForegroundService(context, intent);
+ } else {
+ try {
+ ContextCompat.startForegroundService(context, intent);
+ } catch (ForegroundServiceStartNotAllowedException e) {
+ Log.e(TAG, "Unable to start foreground service", e);
+ throw new UnableToStartException(e);
+ }
+ }
return new NotificationController(context, id);
}
@@ -289,4 +321,10 @@ public final class GenericForegroundService extends Service {
return GenericForegroundService.this;
}
}
+
+ public static final class UnableToStartException extends Exception {
+ public UnableToStartException(Throwable cause) {
+ super(cause);
+ }
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java
index ebab241c8..1b59d5881 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/LocalBackupListener.java
@@ -3,19 +3,27 @@ package org.thoughtcrime.securesms.service;
import android.content.Context;
import android.content.Intent;
+import android.os.Build;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.jobs.LocalBackupJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
+import org.thoughtcrime.securesms.util.JavaTimeExtensionsKt;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
public class LocalBackupListener extends PersistentAlarmManagerListener {
private static final long INTERVAL = TimeUnit.DAYS.toMillis(1);
+ @Override
+ protected boolean scheduleExact() {
+ return Build.VERSION.SDK_INT >= 31;
+ }
+
@Override
protected long getNextScheduledExecutionTime(Context context) {
return TextSecurePreferences.getNextBackupTime(context);
@@ -24,7 +32,7 @@ public class LocalBackupListener extends PersistentAlarmManagerListener {
@Override
protected long onAlarm(Context context, long scheduledTime) {
if (SignalStore.settings().isBackupEnabled()) {
- LocalBackupJob.enqueue(false);
+ LocalBackupJob.enqueue(scheduleExact());
}
return setNextBackupTimeToIntervalFromNow(context);
@@ -37,7 +45,20 @@ public class LocalBackupListener extends PersistentAlarmManagerListener {
}
public static long setNextBackupTimeToIntervalFromNow(@NonNull Context context) {
- long nextTime = System.currentTimeMillis() + INTERVAL;
+ long nextTime;
+
+ if (Build.VERSION.SDK_INT < 31) {
+ nextTime = System.currentTimeMillis() + INTERVAL;
+ } else {
+ LocalDateTime now = LocalDateTime.now();
+ LocalDateTime next = now.withHour(2).withMinute(0).withSecond(0);
+ if (now.getHour() > 2) {
+ next = next.plusDays(1);
+ }
+
+ nextTime = JavaTimeExtensionsKt.toMillis(next);
+ }
+
TextSecurePreferences.setNextBackupTime(context, nextTime);
return nextTime;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java
index d35a3e77a..36f158667 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java
@@ -6,6 +6,7 @@ import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.os.Build;
import org.signal.core.util.PendingIntentFlags;
import org.signal.core.util.logging.Log;
@@ -35,9 +36,21 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
if (pendingIntent != null) {
alarmManager.cancel(pendingIntent);
- alarmManager.set(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
+ if (scheduleExact() && Build.VERSION.SDK_INT >= 31) {
+ if (alarmManager.canScheduleExactAlarms()) {
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
+ } else {
+ Log.w(TAG, "Unable to schedule exact alarm, permissionAllowed: " + alarmManager.canScheduleExactAlarms());
+ }
+ } else {
+ alarmManager.set(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
+ }
} else {
Log.i(TAG, "PendingIntent somehow null, skipping");
}
}
+
+ protected boolean scheduleExact() {
+ return false;
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/JavaTimeExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/util/JavaTimeExtensions.kt
index ceda89be0..2fbcd17ee 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/JavaTimeExtensions.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/JavaTimeExtensions.kt
@@ -26,6 +26,7 @@ fun ZoneId.toOffset(): ZoneOffset {
/**
* Convert [LocalDateTime] to be same as [System.currentTimeMillis]
*/
+@JvmOverloads
fun LocalDateTime.toMillis(zoneOffset: ZoneOffset = ZoneId.systemDefault().toOffset()): Long {
return TimeUnit.SECONDS.toMillis(toEpochSecond(zoneOffset))
}
diff --git a/app/src/main/res/drawable-night/ic_cant_backup_megaphone.xml b/app/src/main/res/drawable-night/ic_cant_backup_megaphone.xml
new file mode 100644
index 000000000..6d80254b6
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_cant_backup_megaphone.xml
@@ -0,0 +1,16 @@
+