Show backup progress as a percentage.

fork-5.53.8
Cody Henthorne 2021-12-14 14:31:44 -05:00 zatwierdzone przez Greyson Parrelli
rodzic 4f73e36d72
commit 8014a70134
15 zmienionych plików z 310 dodań i 46 usunięć

Wyświetl plik

@ -0,0 +1,30 @@
package org.thoughtcrime.securesms.backup
import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.database.GroupReceiptDatabase
import org.thoughtcrime.securesms.database.MmsDatabase
import org.thoughtcrime.securesms.database.SmsDatabase
/**
* Queries used by backup exporter to estimate total counts for various complicated tables.
*/
object BackupCountQueries {
const val mmsCount: String = "SELECT COUNT(*) FROM ${MmsDatabase.TABLE_NAME} WHERE ${MmsDatabase.EXPIRES_IN} <= 0 AND ${MmsDatabase.VIEW_ONCE} <= 0"
const val smsCount: String = "SELECT COUNT(*) FROM ${SmsDatabase.TABLE_NAME} WHERE ${SmsDatabase.EXPIRES_IN} <= 0"
@get:JvmStatic
val groupReceiptCount: String = """
SELECT COUNT(*) FROM ${GroupReceiptDatabase.TABLE_NAME}
INNER JOIN ${MmsDatabase.TABLE_NAME} ON ${GroupReceiptDatabase.TABLE_NAME}.${GroupReceiptDatabase.MMS_ID} = ${MmsDatabase.TABLE_NAME}.${MmsDatabase.ID}
WHERE ${MmsDatabase.TABLE_NAME}.${MmsDatabase.EXPIRES_IN} <= 0 AND ${MmsDatabase.TABLE_NAME}.${MmsDatabase.VIEW_ONCE} <= 0
""".trimIndent()
@get:JvmStatic
val attachmentCount: String = """
SELECT COUNT(*) FROM ${AttachmentDatabase.TABLE_NAME}
INNER JOIN ${MmsDatabase.TABLE_NAME} ON ${AttachmentDatabase.TABLE_NAME}.${AttachmentDatabase.MMS_ID} = ${MmsDatabase.TABLE_NAME}.${MmsDatabase.ID}
WHERE ${MmsDatabase.TABLE_NAME}.${MmsDatabase.EXPIRES_IN} <= 0 AND ${MmsDatabase.TABLE_NAME}.${MmsDatabase.VIEW_ONCE} <= 0
""".trimIndent()
}

Wyświetl plik

@ -13,13 +13,12 @@ import java.security.NoSuchAlgorithmException;
public abstract class FullBackupBase {
@SuppressWarnings("unused")
private static final String TAG = Log.tag(FullBackupBase.class);
private static final int DIGEST_ROUNDS = 250_000;
static class BackupStream {
static @NonNull byte[] getBackupKey(@NonNull String passphrase, @Nullable byte[] salt) {
try {
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0, 0));
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] input = passphrase.replace(" ", "").getBytes();
@ -27,8 +26,8 @@ public abstract class FullBackupBase {
if (salt != null) digest.update(salt);
for (int i=0;i<250000;i++) {
if (i % 1000 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0));
for (int i = 0; i < DIGEST_ROUNDS; i++) {
if (i % 1000 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, 0, 0));
digest.update(hash);
hash = digest.digest(input);
}
@ -47,20 +46,34 @@ public abstract class FullBackupBase {
}
private final Type type;
private final int count;
private final long count;
private final long estimatedTotalCount;
BackupEvent(Type type, int count) {
this.type = type;
this.count = count;
BackupEvent(Type type, long count, long estimatedTotalCount) {
this.type = type;
this.count = count;
this.estimatedTotalCount = estimatedTotalCount;
}
public Type getType() {
return type;
}
public int getCount() {
public long getCount() {
return count;
}
public long getEstimatedTotalCount() {
return estimatedTotalCount;
}
public double getCompletionPercentage() {
if (estimatedTotalCount == 0) {
return 0;
}
return Math.min(99.9f, (double) count * 100L / (double) estimatedTotalCount);
}
}
}

Wyświetl plik

@ -75,6 +75,11 @@ public class FullBackupExporter extends FullBackupBase {
private static final String TAG = Log.tag(FullBackupExporter.class);
private static final long DATABASE_VERSION_RECORD_COUNT = 1L;
private static final long TABLE_RECORD_COUNT_MULTIPLIER = 3L;
private static final long IDENTITY_KEY_BACKUP_RECORD_COUNT = 2L;
private static final long FINAL_MESSAGE_COUNT = 1L;
private static final Set<String> BLACKLISTED_TABLES = SetUtil.newHashSet(
SignedPreKeyDatabase.TABLE_NAME,
OneTimePreKeyDatabase.TABLE_NAME,
@ -134,58 +139,62 @@ public class FullBackupExporter extends FullBackupBase {
@NonNull BackupCancellationSignal cancellationSignal)
throws IOException
{
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(fileOutputStream, passphrase);
int count = 0;
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(fileOutputStream, passphrase);
int count = 0;
long estimatedCountOutside = 0L;
try {
outputStream.writeDatabaseVersion(input.getVersion());
count++;
List<String> tables = exportSchema(input, outputStream);
count += tables.size() * 3;
count += tables.size() * TABLE_RECORD_COUNT_MULTIPLIER;
final long estimatedCount = calculateCount(context, input, tables);
estimatedCountOutside = estimatedCount;
Stopwatch stopwatch = new Stopwatch("Backup");
for (String table : tables) {
throwIfCanceled(cancellationSignal);
if (table.equals(MmsDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count, cancellationSignal);
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringMmsMessage, null, count, estimatedCount, cancellationSignal);
} else if (table.equals(SmsDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count, cancellationSignal);
count = exportTable(table, input, outputStream, FullBackupExporter::isNonExpiringSmsMessage, null, count, estimatedCount, cancellationSignal);
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count, cancellationSignal);
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(GroupReceiptDatabase.MMS_ID))), null, count, estimatedCount, cancellationSignal);
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount), count, cancellationSignal);
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.MMS_ID))), (cursor, innerCount) -> exportAttachment(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
} else if (table.equals(StickerDatabase.TABLE_NAME)) {
count = exportTable(table, input, outputStream, cursor -> true, (cursor, innerCount) -> exportSticker(attachmentSecret, cursor, outputStream, innerCount), count, cancellationSignal);
count = exportTable(table, input, outputStream, cursor -> true, (cursor, innerCount) -> exportSticker(attachmentSecret, cursor, outputStream, innerCount, estimatedCount), count, estimatedCount, cancellationSignal);
} else if (!BLACKLISTED_TABLES.contains(table) && !table.startsWith("sqlite_")) {
count = exportTable(table, input, outputStream, null, null, count, cancellationSignal);
count = exportTable(table, input, outputStream, null, null, count, estimatedCount, cancellationSignal);
}
stopwatch.split("table::" + table);
}
for (BackupProtos.SharedPreference preference : IdentityKeyUtil.getBackupRecord(context)) {
throwIfCanceled(cancellationSignal);
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(preference);
}
for (BackupProtos.SharedPreference preference : TextSecurePreferences.getPreferencesToSaveToBackup(context)) {
throwIfCanceled(cancellationSignal);
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(preference);
}
stopwatch.split("prefs");
count = exportKeyValues(outputStream, SignalStore.getKeysToIncludeInBackup(), count, cancellationSignal);
count = exportKeyValues(outputStream, SignalStore.getKeysToIncludeInBackup(), count, estimatedCount, cancellationSignal);
stopwatch.split("key_values");
for (AvatarHelper.Avatar avatar : AvatarHelper.getAvatars(context)) {
throwIfCanceled(cancellationSignal);
if (avatar != null) {
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(avatar.getFilename(), avatar.getInputStream(), avatar.getLength());
}
}
@ -198,7 +207,49 @@ public class FullBackupExporter extends FullBackupBase {
if (closeOutputStream) {
outputStream.close();
}
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, ++count, estimatedCountOutside));
}
}
private static long calculateCount(@NonNull Context context, @NonNull SQLiteDatabase input, List<String> tables) {
long count = DATABASE_VERSION_RECORD_COUNT + TABLE_RECORD_COUNT_MULTIPLIER * tables.size();
for (String table : tables) {
if (table.equals(MmsDatabase.TABLE_NAME)) {
count += getCount(input, BackupCountQueries.mmsCount);
} else if (table.equals(SmsDatabase.TABLE_NAME)) {
count += getCount(input, BackupCountQueries.smsCount);
} else if (table.equals(GroupReceiptDatabase.TABLE_NAME)) {
count += getCount(input, BackupCountQueries.getGroupReceiptCount());
} else if (table.equals(AttachmentDatabase.TABLE_NAME)) {
count += getCount(input, BackupCountQueries.getAttachmentCount());
} else if (table.equals(StickerDatabase.TABLE_NAME)) {
count += getCount(input, "SELECT COUNT(*) FROM " + table);
} else if (!BLACKLISTED_TABLES.contains(table) && !table.startsWith("sqlite_")) {
count += getCount(input, "SELECT COUNT(*) FROM " + table);
}
}
count += IDENTITY_KEY_BACKUP_RECORD_COUNT;
count += TextSecurePreferences.getPreferencesToSaveToBackupCount(context);
KeyValueDataSet dataSet = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication())
.getDataSet();
for (String key : SignalStore.getKeysToIncludeInBackup()) {
if (dataSet.containsKey(key)) {
count++;
}
}
count += AvatarHelper.getAvatarCount(context);
return count + FINAL_MESSAGE_COUNT;
}
private static long getCount(@NonNull SQLiteDatabase input, @NonNull String query) {
try (Cursor cursor = input.rawQuery(query)) {
return cursor.moveToFirst() ? cursor.getLong(0) : 0;
}
}
@ -244,6 +295,7 @@ public class FullBackupExporter extends FullBackupBase {
@Nullable Predicate<Cursor> predicate,
@Nullable PostProcessor postProcess,
int count,
long estimatedCount,
@NonNull BackupCancellationSignal cancellationSignal)
throws IOException
{
@ -283,7 +335,7 @@ public class FullBackupExporter extends FullBackupBase {
statement.append(')');
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(statementBuilder.setStatement(statement.toString()).build());
if (postProcess != null) {
@ -296,7 +348,7 @@ public class FullBackupExporter extends FullBackupBase {
return count;
}
private static int exportAttachment(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count) {
private static int exportAttachment(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count, long estimatedCount) {
try {
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.ROW_ID));
long uniqueId = cursor.getLong(cursor.getColumnIndexOrThrow(AttachmentDatabase.UNIQUE_ID));
@ -321,7 +373,7 @@ public class FullBackupExporter extends FullBackupBase {
if (random != null && random.length == 32) inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
else inputStream = ClassicDecryptingPartInputStream.createFor(attachmentSecret, new File(data));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(new AttachmentId(rowId, uniqueId), inputStream, size);
}
} catch (IOException e) {
@ -331,7 +383,7 @@ public class FullBackupExporter extends FullBackupBase {
return count;
}
private static int exportSticker(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count) {
private static int exportSticker(@NonNull AttachmentSecret attachmentSecret, @NonNull Cursor cursor, @NonNull BackupFrameOutputStream outputStream, int count, long estimatedCount) {
try {
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase._ID));
long size = cursor.getLong(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_LENGTH));
@ -340,7 +392,7 @@ public class FullBackupExporter extends FullBackupBase {
byte[] random = cursor.getBlob(cursor.getColumnIndexOrThrow(StickerDatabase.FILE_RANDOM));
if (!TextUtils.isEmpty(data) && size > 0) {
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
InputStream inputStream = ModernDecryptingPartInputStream.createFor(attachmentSecret, random, new File(data), 0);
outputStream.writeSticker(rowId, inputStream, size);
}
@ -371,6 +423,7 @@ public class FullBackupExporter extends FullBackupBase {
private static int exportKeyValues(@NonNull BackupFrameOutputStream outputStream,
@NonNull List<String> keysToIncludeInBackup,
int count,
long estimatedCount,
BackupCancellationSignal cancellationSignal) throws IOException
{
KeyValueDataSet dataSet = KeyValueDatabase.getInstance(ApplicationDependencies.getApplication())
@ -401,7 +454,7 @@ public class FullBackupExporter extends FullBackupBase {
throw new AssertionError("Unknown type: " + type);
}
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, ++count, estimatedCount));
outputStream.write(builder.build());
}

Wyświetl plik

@ -95,7 +95,7 @@ public class FullBackupImporter extends FullBackupBase {
BackupFrame frame;
while (!(frame = inputStream.readFrame()).getEnd()) {
if (count % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count));
if (count % 100 == 0) EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.PROGRESS, count, 0));
count++;
if (frame.hasVersion()) processVersion(db, frame.getVersion());
@ -115,7 +115,7 @@ public class FullBackupImporter extends FullBackupBase {
keyValueDatabase.endTransaction();
}
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, count));
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, count, 0));
}
private static @NonNull InputStream getInputStream(@NonNull Context context, @NonNull Uri uri) throws IOException{

Wyświetl plik

@ -59,7 +59,7 @@ final class OldDeviceClientTask implements ClientTask {
public void onEvent(FullBackupBase.BackupEvent event) {
if (event.getType() == FullBackupBase.BackupEvent.Type.PROGRESS) {
if (System.currentTimeMillis() > lastProgressUpdate + PROGRESS_UPDATE_THROTTLE) {
EventBus.getDefault().post(new Status(event.getCount(), false));
EventBus.getDefault().post(new Status(event.getCount(), event.getEstimatedTotalCount(), event.getCompletionPercentage(), false));
lastProgressUpdate = System.currentTimeMillis();
}
}
@ -68,22 +68,34 @@ final class OldDeviceClientTask implements ClientTask {
@Override
public void success() {
SignalStore.misc().markOldDeviceTransferLocked();
EventBus.getDefault().post(new Status(0, true));
EventBus.getDefault().post(new Status(0, 0, 0,true));
}
public static final class Status {
private final long messages;
private final long estimatedMessages;
private final double completionPercentage;
private final boolean done;
public Status(long messages, boolean done) {
this.messages = messages;
this.done = done;
public Status(long messages, long estimatedMessages, double completionPercentage, boolean done) {
this.messages = messages;
this.estimatedMessages = estimatedMessages;
this.completionPercentage = completionPercentage;
this.done = done;
}
public long getMessageCount() {
return messages;
}
public long getEstimatedMessageCount() {
return estimatedMessages;
}
public double getCompletionPercentage() {
return completionPercentage;
}
public boolean isDone() {
return done;
}

Wyświetl plik

@ -15,6 +15,9 @@ import org.signal.devicetransfer.TransferStatus;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.devicetransfer.DeviceTransferFragment;
import java.text.NumberFormat;
import java.util.Locale;
/**
* Shows transfer progress on the old device. Most logic is in {@link DeviceTransferFragment}
* and it delegates to this class for strings, navigation, and updating progress.
@ -52,6 +55,14 @@ public final class OldDeviceTransferFragment extends DeviceTransferFragment {
}
private class ClientTaskListener {
private final NumberFormat formatter;
public ClientTaskListener() {
formatter = NumberFormat.getInstance();
formatter.setMinimumFractionDigits(1);
formatter.setMaximumFractionDigits(1);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(@NonNull OldDeviceClientTask.Status event) {
if (event.isDone()) {
@ -61,7 +72,11 @@ public final class OldDeviceTransferFragment extends DeviceTransferFragment {
DeviceToDeviceTransferService.stop(requireContext());
NavHostFragment.findNavController(OldDeviceTransferFragment.this).navigate(R.id.action_oldDeviceTransfer_to_oldDeviceTransferComplete);
} else {
status.setText(getString(R.string.DeviceTransfer__d_messages_so_far, event.getMessageCount()));
if (event.getEstimatedMessageCount() == 0) {
status.setText(getString(R.string.DeviceTransfer__d_messages_so_far, event.getMessageCount()));
} else {
status.setText(getString(R.string.DeviceTransfer__s_of_messages_so_far, formatter.format(event.getCompletionPercentage())));
}
}
}
}

Wyświetl plik

@ -5,10 +5,14 @@ import android.Manifest;
import androidx.annotation.NonNull;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.backup.BackupFileIOError;
import org.thoughtcrime.securesms.backup.BackupPassphrase;
import org.thoughtcrime.securesms.backup.FullBackupBase;
import org.thoughtcrime.securesms.backup.FullBackupExporter;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.database.NoExternalStorageException;
@ -85,11 +89,14 @@ public final class LocalBackupJob extends BaseJob {
throw new IOException("No external storage permission!");
}
ProgressUpdater updater = new ProgressUpdater();
try (NotificationController notification = GenericForegroundService.startForegroundTask(context,
context.getString(R.string.LocalBackupJob_creating_backup),
context.getString(R.string.LocalBackupJob_creating_signal_backup),
NotificationChannels.BACKUPS,
R.drawable.ic_signal_backup))
{
updater.setNotification(notification);
EventBus.getDefault().register(updater);
notification.setIndeterminateProgress();
String backupPassword = BackupPassphrase.get(context);
@ -139,6 +146,9 @@ public final class LocalBackupJob extends BaseJob {
}
BackupUtil.deleteOldBackups();
} finally {
EventBus.getDefault().unregister(updater);
updater.setNotification(null);
}
}
@ -166,6 +176,29 @@ public final class LocalBackupJob extends BaseJob {
public void onFailure() {
}
private static class ProgressUpdater {
private NotificationController notification;
@Subscribe(threadMode = ThreadMode.POSTING)
public void onEvent(FullBackupBase.BackupEvent event) {
if (notification == null) {
return;
}
if (event.getType() == FullBackupBase.BackupEvent.Type.PROGRESS) {
if (event.getEstimatedTotalCount() == 0) {
notification.setIndeterminateProgress();
} else {
notification.setProgress(100, (int) event.getCompletionPercentage());
}
}
}
public void setNotification(NotificationController notification) {
this.notification = notification;
}
}
public static class Factory implements Job.Factory<LocalBackupJob> {
@Override
public @NonNull LocalBackupJob create(@NonNull Parameters parameters, @NonNull Data data) {

Wyświetl plik

@ -6,10 +6,14 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.documentfile.provider.DocumentFile;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.backup.BackupFileIOError;
import org.thoughtcrime.securesms.backup.BackupPassphrase;
import org.thoughtcrime.securesms.backup.FullBackupBase;
import org.thoughtcrime.securesms.backup.FullBackupExporter;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.database.SignalDatabase;
@ -70,11 +74,14 @@ public final class LocalBackupJobApi29 extends BaseJob {
throw new IOException("Backup Directory has not been selected!");
}
ProgressUpdater updater = new ProgressUpdater();
try (NotificationController notification = GenericForegroundService.startForegroundTask(context,
context.getString(R.string.LocalBackupJob_creating_backup),
context.getString(R.string.LocalBackupJob_creating_signal_backup),
NotificationChannels.BACKUPS,
R.drawable.ic_signal_backup))
{
updater.setNotification(notification);
EventBus.getDefault().register(updater);
notification.setIndeterminateProgress();
String backupPassword = BackupPassphrase.get(context);
@ -135,6 +142,9 @@ public final class LocalBackupJobApi29 extends BaseJob {
}
BackupUtil.deleteOldBackups();
} finally {
EventBus.getDefault().unregister(updater);
updater.setNotification(null);
}
}
@ -162,6 +172,29 @@ public final class LocalBackupJobApi29 extends BaseJob {
public void onFailure() {
}
private static class ProgressUpdater {
private NotificationController notification;
@Subscribe(threadMode = ThreadMode.POSTING)
public void onEvent(FullBackupBase.BackupEvent event) {
if (notification == null) {
return;
}
if (event.getType() == FullBackupBase.BackupEvent.Type.PROGRESS) {
if (event.getEstimatedTotalCount() == 0) {
notification.setIndeterminateProgress();
} else {
notification.setProgress(100, (int) event.getCompletionPercentage());
}
}
}
public void setNotification(NotificationController notification) {
this.notification = notification;
}
}
public static class Factory implements Job.Factory<LocalBackupJobApi29> {
@Override
public @NonNull

Wyświetl plik

@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.BackupUtil;
import org.thoughtcrime.securesms.util.StorageUtil;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Objects;
@ -54,6 +55,8 @@ public class BackupsPreferenceFragment extends Fragment {
private ProgressBar progress;
private TextView progressSummary;
private final NumberFormat formatter = NumberFormat.getInstance();
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_backups, container, false);
@ -75,6 +78,9 @@ public class BackupsPreferenceFragment extends Fragment {
create.setOnClickListener(unused -> onCreateClicked());
verify.setOnClickListener(unused -> BackupDialog.showVerifyBackupPassphraseDialog(requireContext()));
formatter.setMinimumFractionDigits(1);
formatter.setMaximumFractionDigits(1);
EventBus.getDefault().register(this);
}
@ -120,8 +126,19 @@ public class BackupsPreferenceFragment extends Fragment {
create.setEnabled(false);
summary.setText(getString(R.string.BackupsPreferenceFragment__in_progress));
progress.setVisibility(View.VISIBLE);
progressSummary.setVisibility(View.VISIBLE);
progressSummary.setText(getString(R.string.BackupsPreferenceFragment__d_so_far, event.getCount()));
progressSummary.setVisibility(event.getCount() > 0 ? View.VISIBLE : View.GONE);
if (event.getEstimatedTotalCount() == 0) {
progress.setIndeterminate(true);
progressSummary.setText(getString(R.string.BackupsPreferenceFragment__d_so_far, event.getCount()));
} else {
double completionPercentage = event.getCompletionPercentage();
progress.setIndeterminate(false);
progress.setMax(100);
progress.setProgress((int) completionPercentage);
progressSummary.setText(getString(R.string.BackupsPreferenceFragment__s_so_far, formatter.format(completionPercentage)));
}
} else if (event.getType() == FullBackupBase.BackupEvent.Type.FINISHED) {
create.setEnabled(true);
progress.setVisibility(View.GONE);

Wyświetl plik

@ -34,6 +34,13 @@ public class AvatarHelper {
private static final String AVATAR_DIRECTORY = "avatars";
public static long getAvatarCount(@NonNull Context context) {
File avatarDirectory = context.getDir(AVATAR_DIRECTORY, Context.MODE_PRIVATE);
String[] results = avatarDirectory.list();
return results == null ? 0 : results.length;
}
/**
* Retrieves an iterable set of avatars. Only intended to be used during backup.
*/

Wyświetl plik

@ -328,7 +328,7 @@ public final class RestoreBackupFragment extends LoggingFragment {
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(@NonNull FullBackupBase.BackupEvent event) {
int count = event.getCount();
long count = event.getCount();
if (count == 0) {
restoreBackupProgress.setText(R.string.RegistrationActivity_checking);

Wyświetl plik

@ -230,6 +230,31 @@ public class TextSecurePreferences {
MEDIA_DOWNLOAD_WIFI_PREF,
MEDIA_DOWNLOAD_ROAMING_PREF};
public static long getPreferencesToSaveToBackupCount(@NonNull Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
long count = 0;
for (String booleanPreference : booleanPreferencesToBackup) {
if (preferences.contains(booleanPreference)) {
count++;
}
}
for (String stringPreference : stringPreferencesToBackup) {
if (preferences.contains(stringPreference)) {
count++;
}
}
for (String stringSetPreference : stringSetPreferencesToBackup) {
if (preferences.contains(stringSetPreference)) {
count++;
}
}
return count;
}
public static List<BackupProtos.SharedPreference> getPreferencesToSaveToBackup(@NonNull Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
List<BackupProtos.SharedPreference> backupProtos = new ArrayList<>();

Wyświetl plik

@ -64,7 +64,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:indeterminate="true"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/fragment_backup_progress_summary"
app:layout_constraintEnd_toEndOf="parent"

Wyświetl plik

@ -470,6 +470,8 @@
<string name="BackupsPreferenceFragment__learn_more">Learn more</string>
<string name="BackupsPreferenceFragment__in_progress">In progress…</string>
<string name="BackupsPreferenceFragment__d_so_far">%1$d so far…</string>
<!-- Show percentage of completion of backup -->
<string name="BackupsPreferenceFragment__s_so_far">%1$s%% so far…</string>
<string name="BackupsPreferenceFragment_signal_requires_external_storage_permission_in_order_to_create_backups">Signal requires external storage permission in order to create backups, but it has been permanently denied. Please continue to app settings, select \"Permissions\" and enable \"Storage\".</string>
@ -3084,7 +3086,7 @@
<string name="BackupDialog_verify">Verify</string>
<string name="BackupDialog_you_successfully_entered_your_backup_passphrase">You successfully entered your backup passphrase</string>
<string name="BackupDialog_passphrase_was_not_correct">Passphrase was not correct</string>
<string name="LocalBackupJob_creating_backup">Creating backup…</string>
<string name="LocalBackupJob_creating_signal_backup">Creating Signal backup…</string>
<string name="LocalBackupJobApi29_backup_failed">Backup failed</string>
<string name="LocalBackupJobApi29_your_backup_directory_has_been_deleted_or_moved">Your backup directory has been deleted or moved.</string>
<string name="LocalBackupJobApi29_your_backup_file_is_too_large">Your backup file is too large to store on this volume.</string>
@ -3213,6 +3215,8 @@
<string name="DeviceTransfer__transferring_data">Transferring data</string>
<string name="DeviceTransfer__keep_both_devices_near_each_other">Keep both devices near each other. Do not turn off the devices and keep Signal open. Transfers are end-to-end encrypted.</string>
<string name="DeviceTransfer__d_messages_so_far">%1$d messages so far…</string>
<!-- Filled in with total percentage of messages transferred -->
<string name="DeviceTransfer__s_of_messages_so_far">%1$s%% of messages so far…</string>
<string name="DeviceTransfer__cancel">Cancel</string>
<string name="DeviceTransfer__try_again">Try again</string>
<string name="DeviceTransfer__stop_transfer_question">Stop transfer?</string>

Wyświetl plik

@ -8,10 +8,12 @@ import android.content.Intent;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PowerManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
@ -21,6 +23,7 @@ import org.signal.core.util.logging.Log;
import java.io.Serializable;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* Foreground service to help manage interactions with the {@link DeviceTransferClient} and
@ -44,6 +47,7 @@ public class DeviceToDeviceTransferService extends Service implements ShutdownCa
private PendingIntent pendingIntent;
private DeviceTransferServer server;
private DeviceTransferClient client;
private PowerManager.WakeLock wakeLock;
public static void startServer(@NonNull Context context,
@NonNull ServerTask serverTask,
@ -114,6 +118,10 @@ public class DeviceToDeviceTransferService extends Service implements ShutdownCa
server = null;
}
if (wakeLock != null) {
wakeLock.release();
}
super.onDestroy();
}
@ -143,6 +151,7 @@ public class DeviceToDeviceTransferService extends Service implements ShutdownCa
server = new DeviceTransferServer(getApplicationContext(),
(ServerTask) Objects.requireNonNull(intent.getSerializableExtra(EXTRA_TASK)),
this);
acquireWakeLock();
server.start();
} else {
Log.i(TAG, "Can't start server, already started.");
@ -156,6 +165,7 @@ public class DeviceToDeviceTransferService extends Service implements ShutdownCa
client = new DeviceTransferClient(getApplicationContext(),
(ClientTask) Objects.requireNonNull(intent.getSerializableExtra(EXTRA_TASK)),
this);
acquireWakeLock();
client.start();
} else {
Log.i(TAG, "Can't start client, client already started.");
@ -187,6 +197,19 @@ public class DeviceToDeviceTransferService extends Service implements ShutdownCa
});
}
private void acquireWakeLock() {
if (wakeLock == null) {
PowerManager powerManager = ContextCompat.getSystemService(this, PowerManager.class);
if (powerManager != null) {
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:d2dpartial");
}
}
if (!wakeLock.isHeld()) {
wakeLock.acquire(TimeUnit.HOURS.toMillis(2));
}
}
private void updateNotification(@NonNull TransferStatus transferStatus) {
if (notificationData != null && (client != null || server != null)) {
startForeground(notificationData.notificationId, createNotification(transferStatus, notificationData));