Show backup progress as a percentage.
This commit is contained in:
parent
4f73e36d72
commit
8014a70134
15 changed files with 310 additions and 46 deletions
|
@ -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()
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Add table
Reference in a new issue