Fix backup job background start restricitions with API31+.

fork-5.53.8
Cody Henthorne 2022-10-12 09:48:40 -04:00 zatwierdzone przez GitHub
rodzic e1c6dfb73b
commit a8e03e9bf2
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
18 zmienionych plików z 453 dodań i 22 usunięć

Wyświetl plik

@ -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"/>

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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
}
}
}
}

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);

Wyświetl plik

@ -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;

Wyświetl plik

@ -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)))
}
}
}

Wyświetl plik

@ -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);
}

Wyświetl plik

@ -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);
}
}
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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;
}
}

Wyświetl plik

@ -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))
}

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>