kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fix backup job background start restricitions with API31+.
rodzic
e1c6dfb73b
commit
a8e03e9bf2
|
@ -93,6 +93,8 @@
|
|||
|
||||
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
|
||||
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
|
||||
<application android:name=".ApplicationContext"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
@ -878,6 +880,8 @@
|
|||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver android:name="org.thoughtcrime.securesms.jobs.ForegroundUtil$Receiver" android:exported="false" />
|
||||
|
||||
<receiver android:name=".service.PersistentConnectionBootListener" android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
import org.thoughtcrime.securesms.service.NotificationController;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
|
@ -43,17 +44,17 @@ public class SQLCipherMigrationHelper {
|
|||
@NonNull SQLiteDatabase modernDb)
|
||||
{
|
||||
modernDb.beginTransaction();
|
||||
int foregroundId = GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database)).getId();
|
||||
try {
|
||||
try (NotificationController controller = GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database))) {
|
||||
copyTable("identities", legacyDb, modernDb, null);
|
||||
copyTable("push", legacyDb, modernDb, null);
|
||||
copyTable("groups", legacyDb, modernDb, null);
|
||||
copyTable("recipient_preferences", legacyDb, modernDb, null);
|
||||
copyTable("group_receipts", legacyDb, modernDb, null);
|
||||
modernDb.setTransactionSuccessful();
|
||||
} catch (GenericForegroundService.UnableToStartException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} finally {
|
||||
modernDb.endTransaction();
|
||||
GenericForegroundService.stopForegroundTask(context, foregroundId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,8 +69,7 @@ public class SQLCipherMigrationHelper {
|
|||
|
||||
modernDb.beginTransaction();
|
||||
|
||||
int foregroundId = GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database)).getId();
|
||||
try {
|
||||
try (NotificationController controller = GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database))) {
|
||||
int total = 5000;
|
||||
|
||||
copyTable("sms", legacyDb, modernDb, (row, progress) -> {
|
||||
|
@ -176,9 +176,10 @@ public class SQLCipherMigrationHelper {
|
|||
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
|
||||
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
|
||||
modernDb.setTransactionSuccessful();
|
||||
} catch (GenericForegroundService.UnableToStartException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} finally {
|
||||
modernDb.endTransaction();
|
||||
GenericForegroundService.stopForegroundTask(context, foregroundId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ public final class AttachmentCompressionJob extends BaseJob {
|
|||
return attachment;
|
||||
}
|
||||
|
||||
try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) {
|
||||
try (NotificationController notification = ForegroundUtil.requireForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) {
|
||||
|
||||
notification.setIndeterminateProgress();
|
||||
|
||||
|
@ -290,7 +290,7 @@ public final class AttachmentCompressionJob extends BaseJob {
|
|||
throw new UndeliverableMessageException("Failed to transcode and cannot skip due to editing", e);
|
||||
}
|
||||
}
|
||||
} catch (IOException | MmsException e) {
|
||||
} catch (GenericForegroundService.UnableToStartException | IOException | MmsException e) {
|
||||
throw new UndeliverableMessageException("Failed to transcode", e);
|
||||
}
|
||||
return attachment;
|
||||
|
|
|
@ -159,7 +159,12 @@ public final class AttachmentUploadJob extends BaseJob {
|
|||
|
||||
private @Nullable NotificationController getNotificationForAttachment(@NonNull Attachment attachment) {
|
||||
if (attachment.getSize() >= FOREGROUND_LIMIT) {
|
||||
return GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_uploading_media));
|
||||
try {
|
||||
return ForegroundUtil.requireForegroundTask(context, context.getString(R.string.AttachmentUploadJob_uploading_media));
|
||||
} catch (GenericForegroundService.UnableToStartException e) {
|
||||
Log.w(TAG, "Unable to start foreground service", e);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package org.thoughtcrime.securesms.jobs
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.SystemClock
|
||||
import org.signal.core.util.PendingIntentFlags
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService
|
||||
import org.thoughtcrime.securesms.service.NotificationController
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Helps start foreground services from the background.
|
||||
*/
|
||||
object ForegroundUtil {
|
||||
|
||||
private val TAG = Log.tag(ForegroundUtil::class.java)
|
||||
|
||||
private val updateMutex: ReentrantLock = ReentrantLock()
|
||||
private var activeLatch: CountDownLatch? = null
|
||||
|
||||
@Throws(GenericForegroundService.UnableToStartException::class)
|
||||
@JvmStatic
|
||||
fun requireForegroundTask(context: Context, task: String): NotificationController {
|
||||
val alarmManager = ServiceUtil.getAlarmManager(context)
|
||||
|
||||
if (Build.VERSION.SDK_INT < 31 || ApplicationDependencies.getAppForegroundObserver().isForegrounded || !alarmManager.canScheduleExactAlarms()) {
|
||||
return GenericForegroundService.startForegroundTask(context, task)
|
||||
}
|
||||
|
||||
val latch: CountDownLatch? = updateMutex.withLock {
|
||||
if (activeLatch == null) {
|
||||
if (alarmManager.canScheduleExactAlarms()) {
|
||||
activeLatch = CountDownLatch(1)
|
||||
val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(context, Receiver::class.java), PendingIntentFlags.mutable())
|
||||
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 1000, pendingIntent)
|
||||
} else {
|
||||
Log.w(TAG, "Unable to schedule alarm")
|
||||
}
|
||||
}
|
||||
activeLatch
|
||||
}
|
||||
|
||||
if (latch != null) {
|
||||
try {
|
||||
if (!latch.await(1, TimeUnit.MINUTES)) {
|
||||
Log.w(TAG, "Time ran out waiting for foreground")
|
||||
}
|
||||
} catch (e: InterruptedException) {
|
||||
Log.w(TAG, "Interrupted while waiting for foreground")
|
||||
}
|
||||
}
|
||||
|
||||
return GenericForegroundService.startForegroundTask(context, task)
|
||||
}
|
||||
|
||||
class Receiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
updateMutex.withLock {
|
||||
activeLatch?.countDown()
|
||||
activeLatch = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs;
|
|||
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
|
@ -55,7 +56,7 @@ public final class LocalBackupJob extends BaseJob {
|
|||
.setQueue(QUEUE)
|
||||
.setMaxInstancesForFactory(1)
|
||||
.setMaxAttempts(3);
|
||||
if (force) {
|
||||
if (force || Build.VERSION.SDK_INT >= 31) {
|
||||
jobManager.cancelAllInQueue(QUEUE);
|
||||
} else {
|
||||
parameters.addConstraint(ChargingConstraint.KEY);
|
||||
|
@ -164,6 +165,9 @@ public final class LocalBackupJob extends BaseJob {
|
|||
}
|
||||
|
||||
BackupUtil.deleteOldBackups();
|
||||
} catch (GenericForegroundService.UnableToStartException e) {
|
||||
Log.w(TAG, "This should not happen on API < 31");
|
||||
throw new AssertionError(e);
|
||||
} finally {
|
||||
EventBus.getDefault().unregister(updater);
|
||||
updater.setNotification(null);
|
||||
|
|
|
@ -171,6 +171,9 @@ public final class LocalBackupJobApi29 extends BaseJob {
|
|||
}
|
||||
|
||||
BackupUtil.deleteOldBackups();
|
||||
} catch (GenericForegroundService.UnableToStartException e) {
|
||||
Log.w(TAG, "Unable to start foreground backup service", e);
|
||||
BackupFileIOError.UNKNOWN.postNotification(context);
|
||||
} finally {
|
||||
EventBus.getDefault().unregister(updater);
|
||||
updater.setNotification(null);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package org.thoughtcrime.securesms.megaphone;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
|
||||
|
@ -34,6 +36,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.LocaleFeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.PlayServicesUtil;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.thoughtcrime.securesms.wallpaper.ChatWallpaperActivity;
|
||||
|
@ -105,6 +108,7 @@ public final class Megaphones {
|
|||
put(Event.PINS_FOR_ALL, new PinsForAllSchedule());
|
||||
put(Event.CLIENT_DEPRECATED, SignalStore.misc().isClientDeprecated() ? ALWAYS : NEVER);
|
||||
put(Event.NOTIFICATIONS, shouldShowNotificationsMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(30)) : NEVER);
|
||||
put(Event.BACKUP_SCHEDULE_PERMISSION, shouldShowBackupSchedulePermissionMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(3)) : NEVER);
|
||||
put(Event.ONBOARDING, shouldShowOnboardingMegaphone(context) ? ALWAYS : NEVER);
|
||||
put(Event.TURN_OFF_CENSORSHIP_CIRCUMVENTION, shouldShowTurnOffCircumventionMegaphone() ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(7)) : NEVER);
|
||||
put(Event.DONATE_Q2_2022, shouldShowDonateMegaphone(context, Event.DONATE_Q2_2022, records) ? ShowForDurationSchedule.showForDays(7) : NEVER);
|
||||
|
@ -138,6 +142,8 @@ public final class Megaphones {
|
|||
return buildTurnOffCircumventionMegaphone(context);
|
||||
case REMOTE_MEGAPHONE:
|
||||
return buildRemoteMegaphone(context);
|
||||
case BACKUP_SCHEDULE_PERMISSION:
|
||||
return buildBackupPermissionMegaphone(context);
|
||||
default:
|
||||
throw new IllegalArgumentException("Event not handled!");
|
||||
}
|
||||
|
@ -335,6 +341,21 @@ public final class Megaphones {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
private static Megaphone buildBackupPermissionMegaphone(@NonNull Context context) {
|
||||
return new Megaphone.Builder(Event.BACKUP_SCHEDULE_PERMISSION, Megaphone.Style.BASIC)
|
||||
.setTitle(R.string.BackupSchedulePermissionMegaphone__cant_back_up_chats)
|
||||
.setImage(R.drawable.ic_cant_backup_megaphone)
|
||||
.setBody(R.string.BackupSchedulePermissionMegaphone__your_chats_are_no_longer_being_automatically_backed_up)
|
||||
.setActionButton(R.string.BackupSchedulePermissionMegaphone__back_up_chats, (megaphone, controller) -> {
|
||||
controller.onMegaphoneDialogFragmentRequested(new ReenableBackupsDialogFragment());
|
||||
})
|
||||
.setSecondaryButton(R.string.BackupSchedulePermissionMegaphone__not_now, (megaphone, controller) -> {
|
||||
controller.onMegaphoneSnooze(Event.BACKUP_SCHEDULE_PERMISSION);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private static boolean shouldShowDonateMegaphone(@NonNull Context context, @NonNull Event event, @NonNull Map<Event, MegaphoneRecord> records) {
|
||||
long timeSinceLastDonatePrompt = timeSinceLastDonatePrompt(event, records);
|
||||
|
||||
|
@ -398,6 +419,10 @@ public final class Megaphones {
|
|||
return RemoteMegaphoneRepository.hasRemoteMegaphoneToShow(canShowLocalDonate);
|
||||
}
|
||||
|
||||
private static boolean shouldShowBackupSchedulePermissionMegaphone(@NonNull Context context) {
|
||||
return Build.VERSION.SDK_INT >= 31 && SignalStore.settings().isBackupEnabled() && !ServiceUtil.getAlarmManager(context).canScheduleExactAlarms();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfortunately lastSeen is only set today upon snoozing, which never happens to donate prompts.
|
||||
* So we use firstVisible as a proxy.
|
||||
|
@ -426,7 +451,8 @@ public final class Megaphones {
|
|||
BECOME_A_SUSTAINER("become_a_sustainer"),
|
||||
DONATE_Q2_2022("donate_q2_2022"),
|
||||
TURN_OFF_CENSORSHIP_CIRCUMVENTION("turn_off_censorship_circumvention"),
|
||||
REMOTE_MEGAPHONE("remote_megaphone");
|
||||
REMOTE_MEGAPHONE("remote_megaphone"),
|
||||
BACKUP_SCHEDULE_PERMISSION("backup_schedule_permission");
|
||||
|
||||
private final String key;
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package org.thoughtcrime.securesms.megaphone
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment
|
||||
|
||||
/**
|
||||
* Bottom sheet dialog to prompt user to enable schedule alarms permission for triggering backups.
|
||||
*/
|
||||
class ReenableBackupsDialogFragment : FixedRoundedCornerBottomSheetDialogFragment() {
|
||||
|
||||
override val peekHeightPercentage: Float = 1f
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.reenable_backups_dialog_fragment, container, false)
|
||||
}
|
||||
|
||||
@SuppressLint("InlinedApi")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val launcher: ActivityResultLauncher<Intent> = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
|
||||
view.findViewById<View>(R.id.reenable_backups_go_to_settings).setOnClickListener {
|
||||
launcher.launch(Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM, Uri.parse("package:" + requireContext().packageName)))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,7 +38,6 @@ import org.thoughtcrime.securesms.util.StorageUtil;
|
|||
import java.text.NumberFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class BackupsPreferenceFragment extends Fragment {
|
||||
|
||||
|
@ -85,7 +84,6 @@ public class BackupsPreferenceFragment extends Fragment {
|
|||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
@ -241,7 +239,7 @@ public class BackupsPreferenceFragment extends Fragment {
|
|||
|
||||
@RequiresApi(29)
|
||||
private void onCreateClickedApi29() {
|
||||
Log.i(TAG, "Queing backup...");
|
||||
Log.i(TAG, "Queueing backup...");
|
||||
LocalBackupJob.enqueue(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.app.ForegroundServiceStartNotAllowedException;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
|
@ -18,6 +20,7 @@ import org.signal.core.util.logging.Log;
|
|||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.whispersystems.signalservice.api.util.Preconditions;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -126,20 +129,39 @@ public final class GenericForegroundService extends Service {
|
|||
* Waits for {@param delayMillis} ms before starting the foreground task.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="64"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:pathData="M32,32m-32,0a32,32 0,1 1,64 0a32,32 0,1 1,-64 0"
|
||||
android:fillColor="#1B1C1F"/>
|
||||
<path
|
||||
android:pathData="M32,32m-32,0a32,32 0,1 1,64 0a32,32 0,1 1,-64 0"
|
||||
android:fillColor="#B6C5FA"
|
||||
android:fillAlpha="0.05"/>
|
||||
<path
|
||||
android:pathData="M45.958,49L40.75,43.833C39.5,44.778 38.125,45.514 36.625,46.042C35.125,46.569 33.528,46.833 31.833,46.833C29.778,46.833 27.847,46.444 26.042,45.667C24.236,44.889 22.667,43.826 21.333,42.479C20,41.132 18.938,39.556 18.146,37.75C17.354,35.944 16.958,34 16.958,31.917C16.958,30.222 17.222,28.632 17.75,27.146C18.278,25.66 19.014,24.292 19.958,23.042L15,18.042L16.708,16.292L47.708,47.292L45.958,49ZM31.875,44.417C33.208,44.417 34.472,44.208 35.667,43.792C36.861,43.375 37.958,42.792 38.958,42.042L21.708,24.792C20.958,25.792 20.389,26.882 20,28.063C19.611,29.243 19.417,30.5 19.417,31.833C19.417,35.306 20.618,38.271 23.021,40.729C25.424,43.188 28.375,44.417 31.875,44.417ZM37.042,26.708V24.292H41.458C40.264,22.847 38.826,21.701 37.146,20.854C35.465,20.007 33.694,19.583 31.833,19.583C30.639,19.583 29.521,19.729 28.479,20.021C27.438,20.313 26.458,20.75 25.542,21.333L23.792,19.5C24.958,18.778 26.215,18.208 27.563,17.792C28.91,17.375 30.333,17.167 31.833,17.167C34,17.167 36.076,17.632 38.063,18.563C40.049,19.493 41.75,20.819 43.167,22.542V18.167H45.583V26.708H37.042ZM44.25,40.042L42.417,38.208C42.972,37.347 43.389,36.438 43.667,35.479C43.944,34.521 44.097,33.5 44.125,32.417H46.625C46.569,33.861 46.347,35.222 45.958,36.5C45.569,37.778 45,38.958 44.25,40.042Z"
|
||||
android:fillColor="#B6C5FA"/>
|
||||
</vector>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="64dp"
|
||||
android:height="64dp"
|
||||
android:viewportWidth="64"
|
||||
android:viewportHeight="64">
|
||||
<path
|
||||
android:pathData="M32,32m-32,0a32,32 0,1 1,64 0a32,32 0,1 1,-64 0"
|
||||
android:fillColor="#FBFCFE"/>
|
||||
<path
|
||||
android:pathData="M32,32m-32,0a32,32 0,1 1,64 0a32,32 0,1 1,-64 0"
|
||||
android:fillColor="#50679F"
|
||||
android:fillAlpha="0.05"/>
|
||||
<path
|
||||
android:pathData="M45.958,49L40.75,43.833C39.5,44.778 38.125,45.514 36.625,46.042C35.125,46.569 33.528,46.833 31.833,46.833C29.778,46.833 27.847,46.444 26.042,45.667C24.236,44.889 22.667,43.826 21.333,42.479C20,41.132 18.938,39.556 18.146,37.75C17.354,35.944 16.958,34 16.958,31.917C16.958,30.222 17.222,28.632 17.75,27.146C18.278,25.66 19.014,24.292 19.958,23.042L15,18.042L16.708,16.292L47.708,47.292L45.958,49ZM31.875,44.417C33.208,44.417 34.472,44.208 35.667,43.792C36.861,43.375 37.958,42.792 38.958,42.042L21.708,24.792C20.958,25.792 20.389,26.882 20,28.063C19.611,29.243 19.417,30.5 19.417,31.833C19.417,35.306 20.618,38.271 23.021,40.729C25.424,43.188 28.375,44.417 31.875,44.417ZM37.042,26.708V24.292H41.458C40.264,22.847 38.826,21.701 37.146,20.854C35.465,20.007 33.694,19.583 31.833,19.583C30.639,19.583 29.521,19.729 28.479,20.021C27.438,20.313 26.458,20.75 25.542,21.333L23.792,19.5C24.958,18.778 26.215,18.208 27.563,17.792C28.91,17.375 30.333,17.167 31.833,17.167C34,17.167 36.076,17.632 38.063,18.563C40.049,19.493 41.75,20.819 43.167,22.542V18.167H45.583V26.708H37.042ZM44.25,40.042L42.417,38.208C42.972,37.347 43.389,36.438 43.667,35.479C43.944,34.521 44.097,33.5 44.125,32.417H46.625C46.569,33.861 46.347,35.222 45.958,36.5C45.569,37.778 45,38.958 44.25,40.042Z"
|
||||
android:fillColor="#2C58C3"/>
|
||||
</vector>
|
|
@ -0,0 +1,153 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/handle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:importantForAccessibility="no"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/bottom_sheet_handle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/headline"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/BackupSchedulePermissionMegaphone__to_reenable_backups"
|
||||
android:textAppearance="@style/Signal.Text.HeadlineMedium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:backgroundTint="@color/signal_colorSurface3"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/bullet_1"
|
||||
app:layout_constraintEnd_toEndOf="@+id/bullet_1"
|
||||
app:layout_constraintStart_toStartOf="@+id/bullet_1"
|
||||
app:layout_constraintTop_toTopOf="@+id/bullet_1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bullet_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center"
|
||||
android:minWidth="28dp"
|
||||
android:minHeight="28dp"
|
||||
android:padding="4dp"
|
||||
android:text="@string/ChooseANewDefaultSmsAppFragment__bullet_1"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/headline" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bullet_1_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="23dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:minWidth="28dp"
|
||||
android:minHeight="28dp"
|
||||
android:padding="4dp"
|
||||
android:text="@string/BackupSchedulePermissionMegaphone__tap_the_go_to_settings_button_below"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/bullet_1"
|
||||
app:layout_constraintTop_toBottomOf="@id/headline" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/bullet_1_barrier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:orientation="horizontal"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="bullet_1,bullet_1_text" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:backgroundTint="@color/signal_colorSurface3"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/bullet_2"
|
||||
app:layout_constraintEnd_toEndOf="@+id/bullet_2"
|
||||
app:layout_constraintStart_toStartOf="@+id/bullet_2"
|
||||
app:layout_constraintTop_toTopOf="@+id/bullet_2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bullet_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center"
|
||||
android:minWidth="28dp"
|
||||
android:minHeight="28dp"
|
||||
android:padding="4dp"
|
||||
android:text="@string/ChooseANewDefaultSmsAppFragment__bullet_2"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/bullet_1_barrier" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bullet_2_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="23dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:minWidth="28dp"
|
||||
android:minHeight="28dp"
|
||||
android:padding="4dp"
|
||||
android:text="@string/BackupSchedulePermissionMegaphone__turn_on_allow_settings_alarms_and_reminders"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/Signal.Text.BodyLarge"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/bullet_2"
|
||||
app:layout_constraintTop_toBottomOf="@id/bullet_1_barrier" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/bullet_2_barrier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:orientation="horizontal"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="bullet_2,bullet_2_text" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Signal.Widget.Button.Large.Tonal"
|
||||
android:id="@+id/reenable_backups_go_to_settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="72dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/BackupSchedulePermissionMegaphone__go_to_settings"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/bullet_2_barrier" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -5345,6 +5345,24 @@
|
|||
<!-- Button label to start export -->
|
||||
<string name="SetSignalAsDefaultSmsAppFragment__next">Next</string>
|
||||
|
||||
<!-- BackupSchedulePermission Megaphone -->
|
||||
<!-- Can't backup chats megaphone title -->
|
||||
<string name="BackupSchedulePermissionMegaphone__cant_back_up_chats">Can\'t back up chats</string>
|
||||
<!-- Can't backup chats megaphone message -->
|
||||
<string name="BackupSchedulePermissionMegaphone__your_chats_are_no_longer_being_automatically_backed_up">Your chats are no longer being automatically backed up.</string>
|
||||
<!-- Can't backup chats megaphone button text to take steps to re-enable -->
|
||||
<string name="BackupSchedulePermissionMegaphone__back_up_chats">Back up chats</string>
|
||||
<!-- Can't backup chats megaphone button to snooze megaphone and be reshown later -->
|
||||
<string name="BackupSchedulePermissionMegaphone__not_now">Not now</string>
|
||||
<!-- Re-enable backup permission bottom sheet title -->
|
||||
<string name="BackupSchedulePermissionMegaphone__to_reenable_backups">To re-enable backups:</string>
|
||||
<!-- Re-enable backups permission bottom sheet instruction 1 text -->
|
||||
<string name="BackupSchedulePermissionMegaphone__tap_the_go_to_settings_button_below">Tap the \"Go to settings\" button below</string>
|
||||
<!-- Re-enable backups permission bottom sheet instruction 2 text -->
|
||||
<string name="BackupSchedulePermissionMegaphone__turn_on_allow_settings_alarms_and_reminders">Turn on \"Allow settings alarms and reminders.\"</string>
|
||||
<!-- Re-enable backups permission bottom sheet call to action button to open settings -->
|
||||
<string name="BackupSchedulePermissionMegaphone__go_to_settings">Go to settings</string>
|
||||
|
||||
<!-- EOF -->
|
||||
|
||||
</resources>
|
||||
|
|
Ładowanie…
Reference in New Issue