Add trim conversations by time option.
This commit is contained in:
parent
6a14dc69c0
commit
bcd27355f9
36 changed files with 1183 additions and 233 deletions
|
@ -15,7 +15,9 @@ import net.sqlcipher.database.SQLiteDatabase;
|
|||
import net.sqlcipher.database.SQLiteStatement;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -29,13 +31,23 @@ import java.util.Map;
|
|||
*/
|
||||
public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdapter.Descriptor> {
|
||||
|
||||
private static final String TAG = Log.tag(FlipperSqlCipherAdapter.class);
|
||||
|
||||
public FlipperSqlCipherAdapter(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Descriptor> getDatabases() {
|
||||
return Collections.singletonList(new Descriptor(DatabaseFactory.getRawDatabase(getContext())));
|
||||
try {
|
||||
Field databaseHelperField = DatabaseFactory.class.getDeclaredField("databaseHelper");
|
||||
databaseHelperField.setAccessible(true);
|
||||
SQLCipherOpenHelper sqlCipherOpenHelper = (SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext()));
|
||||
return Collections.singletonList(new Descriptor(sqlCipherOpenHelper));
|
||||
} catch (Exception e) {
|
||||
Log.i(TAG, "Unable to use reflection to access raw database.", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -640,6 +640,8 @@
|
|||
|
||||
<receiver android:name=".revealable.ViewOnceMessageManager$ViewOnceAlarm" />
|
||||
|
||||
<receiver android:name=".service.TrimThreadsByDateManager$TrimThreadsByDateAlarm" />
|
||||
|
||||
<provider android:name=".providers.PartProvider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false"
|
||||
|
|
|
@ -142,6 +142,14 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||
}
|
||||
}
|
||||
|
||||
public void pushFragment(@NonNull Fragment fragment) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
|
||||
.replace(android.R.id.content, fragment)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
}
|
||||
|
||||
public static class ApplicationPreferenceFragment extends CorrectedPreferenceFragment {
|
||||
|
||||
@Override
|
||||
|
@ -292,14 +300,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||
Bundle args = new Bundle();
|
||||
fragment.setArguments(args);
|
||||
|
||||
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
|
||||
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
|
||||
|
||||
fragmentTransaction.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end);
|
||||
|
||||
fragmentTransaction.replace(android.R.id.content, fragment);
|
||||
fragmentTransaction.addToBackStack(null);
|
||||
fragmentTransaction.commit();
|
||||
((ApplicationPreferencesActivity) requireActivity()).pushFragment(fragment);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -91,7 +91,7 @@ public abstract class BaseActivity extends AppCompatActivity {
|
|||
Log.d(TAG, "[" + Log.tag(getClass()) + "] " + event);
|
||||
}
|
||||
|
||||
protected final @NonNull ActionBar requireSupportActionBar() {
|
||||
public final @NonNull ActionBar requireSupportActionBar() {
|
||||
return Objects.requireNonNull(getSupportActionBar());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package org.thoughtcrime.securesms.components.settings;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter;
|
||||
|
||||
/**
|
||||
* Reusable adapter for generic settings list.
|
||||
*/
|
||||
public class BaseSettingsAdapter extends MappingAdapter {
|
||||
public void configureSingleSelect(@NonNull SingleSelectSetting.SingleSelectSelectionChangedListener selectionChangedListener) {
|
||||
registerFactory(SingleSelectSetting.Item.class,
|
||||
new LayoutFactory<>(v -> new SingleSelectSetting.ViewHolder(v, selectionChangedListener), R.layout.single_select_item));
|
||||
}
|
||||
|
||||
public void configureCustomizableSingleSelect(@NonNull CustomizableSingleSelectSetting.CustomizableSingleSelectionListener selectionListener) {
|
||||
registerFactory(CustomizableSingleSelectSetting.Item.class,
|
||||
new LayoutFactory<>(v -> new CustomizableSingleSelectSetting.ViewHolder(v, selectionListener), R.layout.customizable_single_select_item));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package org.thoughtcrime.securesms.components.settings;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.MappingModelList;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A simple settings screen that takes its configuration via {@link Configuration}.
|
||||
*/
|
||||
public class BaseSettingsFragment extends Fragment {
|
||||
|
||||
private static final String CONFIGURATION_ARGUMENT = "current_selection";
|
||||
|
||||
private RecyclerView recycler;
|
||||
|
||||
public static @NonNull BaseSettingsFragment create(@NonNull Configuration configuration) {
|
||||
BaseSettingsFragment fragment = new BaseSettingsFragment();
|
||||
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putSerializable(CONFIGURATION_ARGUMENT, configuration);
|
||||
fragment.setArguments(arguments);
|
||||
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.base_settings_fragment, container, false);
|
||||
|
||||
recycler = view.findViewById(R.id.base_settings_list);
|
||||
recycler.setItemAnimator(null);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
BaseSettingsAdapter adapter = new BaseSettingsAdapter();
|
||||
|
||||
recycler.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
recycler.setAdapter(adapter);
|
||||
|
||||
Configuration configuration = (Configuration) Objects.requireNonNull(requireArguments().getSerializable(CONFIGURATION_ARGUMENT));
|
||||
configuration.configure(requireActivity(), adapter);
|
||||
configuration.setArguments(getArguments());
|
||||
configuration.configureAdapter(adapter);
|
||||
|
||||
adapter.submitList(configuration.getSettings());
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration for a settings screen. Utilizes serializable to hide
|
||||
* reflection of instantiating from a fragment argument.
|
||||
*/
|
||||
public static abstract class Configuration implements Serializable {
|
||||
protected transient FragmentActivity activity;
|
||||
protected transient BaseSettingsAdapter adapter;
|
||||
|
||||
public void configure(@NonNull FragmentActivity activity, @NonNull BaseSettingsAdapter adapter) {
|
||||
this.activity = activity;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve any runtime information from the fragment's arguments.
|
||||
*/
|
||||
public void setArguments(@Nullable Bundle arguments) {}
|
||||
|
||||
protected void updateSettingsList() {
|
||||
adapter.submitList(getSettings());
|
||||
}
|
||||
|
||||
public abstract void configureAdapter(@NonNull BaseSettingsAdapter adapter);
|
||||
|
||||
public abstract @NonNull MappingModelList getSettings();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package org.thoughtcrime.securesms.components.settings;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.MappingModel;
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Adds ability to customize a value for a single select (radio) setting.
|
||||
*/
|
||||
public class CustomizableSingleSelectSetting {
|
||||
|
||||
public interface CustomizableSingleSelectionListener extends SingleSelectSetting.SingleSelectSelectionChangedListener {
|
||||
void onCustomizeClicked(@NonNull Item item);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends MappingViewHolder<Item> {
|
||||
private final TextView summaryText;
|
||||
private final View customize;
|
||||
private final RadioButton radio;
|
||||
private final SingleSelectSetting.ViewHolder delegate;
|
||||
private final Group customizeGroup;
|
||||
private final CustomizableSingleSelectionListener selectionListener;
|
||||
|
||||
public ViewHolder(@NonNull View itemView, @NonNull CustomizableSingleSelectionListener selectionListener) {
|
||||
super(itemView);
|
||||
this.selectionListener = selectionListener;
|
||||
|
||||
radio = findViewById(R.id.customizable_single_select_radio);
|
||||
summaryText = findViewById(R.id.customizable_single_select_summary);
|
||||
customize = findViewById(R.id.customizable_single_select_customize);
|
||||
customizeGroup = findViewById(R.id.customizable_single_select_customize_group);
|
||||
|
||||
delegate = new SingleSelectSetting.ViewHolder(itemView, selectionListener) {
|
||||
@Override
|
||||
protected void setChecked(boolean checked) {
|
||||
radio.setChecked(checked);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull Item model) {
|
||||
delegate.bind(model.singleSelectItem);
|
||||
customizeGroup.setVisibility(radio.isChecked() ? View.VISIBLE : View.GONE);
|
||||
customize.setOnClickListener(v -> selectionListener.onCustomizeClicked(model));
|
||||
if (model.getCustomValue() != null) {
|
||||
summaryText.setText(model.getSummaryText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Item implements MappingModel<Item> {
|
||||
private SingleSelectSetting.Item singleSelectItem;
|
||||
private Object customValue;
|
||||
private String summaryText;
|
||||
|
||||
public <T> Item(@NonNull T item, @Nullable String text, boolean isSelected, @Nullable Object customValue, @Nullable String summaryText) {
|
||||
this.customValue = customValue;
|
||||
this.summaryText = summaryText;
|
||||
|
||||
singleSelectItem = new SingleSelectSetting.Item(item, text, isSelected);
|
||||
}
|
||||
|
||||
public @Nullable Object getCustomValue() {
|
||||
return customValue;
|
||||
}
|
||||
|
||||
public @Nullable String getSummaryText() {
|
||||
return summaryText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Item newItem) {
|
||||
return singleSelectItem.areItemsTheSame(newItem.singleSelectItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Item newItem) {
|
||||
return singleSelectItem.areContentsTheSame(newItem.singleSelectItem) && Objects.equals(customValue, newItem.customValue) && Objects.equals(summaryText, newItem.summaryText);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package org.thoughtcrime.securesms.components.settings;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.CheckedTextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.MappingModel;
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Single select (radio) setting option
|
||||
*/
|
||||
public class SingleSelectSetting {
|
||||
|
||||
public interface SingleSelectSelectionChangedListener {
|
||||
void onSelectionChanged(@NonNull Object selection);
|
||||
}
|
||||
|
||||
public static class ViewHolder extends MappingViewHolder<Item> {
|
||||
|
||||
protected final CheckedTextView text;
|
||||
protected final SingleSelectSelectionChangedListener selectionChangedListener;
|
||||
|
||||
public ViewHolder(@NonNull View itemView, @NonNull SingleSelectSelectionChangedListener selectionChangedListener) {
|
||||
super(itemView);
|
||||
this.selectionChangedListener = selectionChangedListener;
|
||||
this.text = findViewById(R.id.single_select_item_text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(@NonNull Item model) {
|
||||
text.setText(model.text);
|
||||
setChecked(model.isSelected);
|
||||
itemView.setOnClickListener(v -> selectionChangedListener.onSelectionChanged(model.item));
|
||||
}
|
||||
|
||||
protected void setChecked(boolean checked) {
|
||||
text.setChecked(checked);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Item implements MappingModel<Item> {
|
||||
private final String text;
|
||||
private final Object item;
|
||||
private final boolean isSelected;
|
||||
|
||||
public <T> Item(@NonNull T item, @Nullable String text, boolean isSelected) {
|
||||
this.item = item;
|
||||
this.text = text != null ? text : item.toString();
|
||||
this.isSelected = isSelected;
|
||||
}
|
||||
|
||||
public @NonNull String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public @NonNull Object getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull Item newItem) {
|
||||
return item.equals(newItem.item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull Item newItem) {
|
||||
return Objects.equals(text, newItem.text) && isSelected == newItem.isSelected;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -66,6 +66,7 @@ import org.thoughtcrime.securesms.util.JsonUtils;
|
|||
import org.thoughtcrime.securesms.util.MediaMetadataRetrieverUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil.ThumbnailData;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.video.EncryptedMediaDataSource;
|
||||
|
@ -83,10 +84,12 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
@ -481,6 +484,39 @@ public class AttachmentDatabase extends Database {
|
|||
}
|
||||
}
|
||||
|
||||
public void trimAllAbandonedAttachments() {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
String selectAllMmsIds = "SELECT " + MmsDatabase.ID + " FROM " + MmsDatabase.TABLE_NAME;
|
||||
String selectDataInUse = "SELECT DISTINCT " + DATA + " FROM " + TABLE_NAME + " WHERE " + QUOTE + " = 0 AND " + MMS_ID + " IN (" + selectAllMmsIds + ")";
|
||||
String where = MMS_ID + " NOT IN (" + selectAllMmsIds + ") AND " + DATA + " NOT IN (" + selectDataInUse + ")";
|
||||
|
||||
db.delete(TABLE_NAME, where, null);
|
||||
}
|
||||
|
||||
public void deleteAbandonedAttachmentFiles() {
|
||||
Set<String> filesOnDisk = new HashSet<>();
|
||||
Set<String> filesInDb = new HashSet<>();
|
||||
|
||||
File attachmentDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
||||
for (File file : attachmentDirectory.listFiles()) {
|
||||
filesOnDisk.add(file.getAbsolutePath());
|
||||
}
|
||||
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(true, TABLE_NAME, new String[] { DATA, THUMBNAIL }, null, null, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
filesInDb.add(CursorUtil.requireString(cursor, DATA));
|
||||
filesInDb.add(CursorUtil.requireString(cursor, THUMBNAIL));
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> onDiskButNotInDatabase = SetUtil.difference(filesOnDisk, filesInDb);
|
||||
|
||||
for (String filePath : onDiskButNotInDatabase) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
new File(filePath).delete();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
void deleteAllAttachments() {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
|
|
|
@ -115,6 +115,11 @@ public class GroupReceiptDatabase extends Database {
|
|||
db.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {String.valueOf(mmsId)});
|
||||
}
|
||||
|
||||
void deleteAbandonedRows() {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.delete(TABLE_NAME, MMS_ID + " NOT IN (SELECT " + MmsDatabase.ID + " FROM " + MmsDatabase.TABLE_NAME + ")", null);
|
||||
}
|
||||
|
||||
void deleteAllRows() {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.delete(TABLE_NAME, null, null);
|
||||
|
|
|
@ -21,9 +21,9 @@ import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
|
|||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList;
|
||||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||
import org.thoughtcrime.securesms.insights.InsightsConstants;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
|
@ -143,6 +143,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
|
|||
abstract void deleteMessagesInThreadBeforeDate(long threadId, long date);
|
||||
abstract void deleteThreads(@NonNull Set<Long> threadIds);
|
||||
abstract void deleteAllThreads();
|
||||
abstract void deleteAbandonedMessages();
|
||||
|
||||
public abstract SQLiteDatabase beginTransaction();
|
||||
public abstract void endTransaction(SQLiteDatabase database);
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.thoughtcrime.securesms.database;
|
|||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -41,7 +40,6 @@ import org.thoughtcrime.securesms.attachments.AttachmentId;
|
|||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment;
|
||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
||||
import org.thoughtcrime.securesms.database.documents.Document;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatchList;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
|
@ -79,11 +77,9 @@ import org.thoughtcrime.securesms.util.JsonUtils;
|
|||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
|
@ -1588,29 +1584,18 @@ public class MmsDatabase extends MessageDatabase {
|
|||
|
||||
@Override
|
||||
void deleteMessagesInThreadBeforeDate(long threadId, long date) {
|
||||
Cursor cursor = null;
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
String where = THREAD_ID + " = ? AND " + DATE_RECEIVED + " < " + date;
|
||||
|
||||
try {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
String where = THREAD_ID + " = ? AND (CASE (" + MESSAGE_BOX + " & " + Types.BASE_TYPE_MASK + ") ";
|
||||
db.delete(TABLE_NAME, where, SqlUtil.buildArgs(threadId));
|
||||
}
|
||||
|
||||
for (long outgoingType : Types.OUTGOING_MESSAGE_TYPES) {
|
||||
where += " WHEN " + outgoingType + " THEN " + DATE_SENT + " < " + date;
|
||||
}
|
||||
@Override
|
||||
void deleteAbandonedMessages() {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
String where = THREAD_ID + " NOT IN (SELECT _id FROM " + ThreadDatabase.TABLE_NAME + ")";
|
||||
|
||||
where += (" ELSE " + DATE_RECEIVED + " < " + date + " END)");
|
||||
|
||||
cursor = db.query(TABLE_NAME, new String[] {ID}, where, new String[] {threadId+""}, null, null, null);
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
Log.i(TAG, "Trimming: " + cursor.getLong(0));
|
||||
deleteMessage(cursor.getLong(0));
|
||||
}
|
||||
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
}
|
||||
db.delete(TABLE_NAME, where, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -272,6 +272,18 @@ public class MmsSmsDatabase extends Database {
|
|||
return count;
|
||||
}
|
||||
|
||||
public int getMessageCountBeforeDate(long date) {
|
||||
String selection = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " < " + date;
|
||||
|
||||
try (Cursor cursor = queryTables(new String[] { "COUNT(*)" }, selection, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int getSecureMessageCountForInsights() {
|
||||
int count = DatabaseFactory.getSmsDatabase(context).getSecureMessageCountForInsights();
|
||||
count += DatabaseFactory.getMmsDatabase(context).getSecureMessageCountForInsights();
|
||||
|
@ -362,6 +374,29 @@ public class MmsSmsDatabase extends Database {
|
|||
return -1;
|
||||
}
|
||||
|
||||
public long getTimestampForFirstMessageAfterDate(long date) {
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||
String selection = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " > " + date;
|
||||
|
||||
try (Cursor cursor = queryTables(new String[] { MmsSmsColumns.NORMALIZED_DATE_RECEIVED }, selection, order, "1")) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getLong(0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void deleteMessagesInThreadBeforeDate(long threadId, long trimBeforeDate) {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, trimBeforeDate);
|
||||
DatabaseFactory.getMmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, trimBeforeDate);
|
||||
}
|
||||
|
||||
public void deleteAbandonedMessages() {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteAbandonedMessages();
|
||||
DatabaseFactory.getMmsDatabase(context).deleteAbandonedMessages();
|
||||
}
|
||||
|
||||
private Cursor queryTables(String[] projection, String selection, String order, String limit) {
|
||||
String[] mmsProjection = {MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
|
|
|
@ -968,16 +968,18 @@ public class SmsDatabase extends MessageDatabase {
|
|||
|
||||
@Override
|
||||
void deleteMessagesInThreadBeforeDate(long threadId, long date) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
String where = THREAD_ID + " = ? AND (CASE " + TYPE;
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
String where = THREAD_ID + " = ? AND " + DATE_RECEIVED + " < " + date;
|
||||
|
||||
for (long outgoingType : Types.OUTGOING_MESSAGE_TYPES) {
|
||||
where += " WHEN " + outgoingType + " THEN " + DATE_SENT + " < " + date;
|
||||
}
|
||||
db.delete(TABLE_NAME, where, SqlUtil.buildArgs(threadId));
|
||||
}
|
||||
|
||||
where += (" ELSE " + DATE_RECEIVED + " < " + date + " END)");
|
||||
@Override
|
||||
void deleteAbandonedMessages() {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
String where = THREAD_ID + " NOT IN (SELECT _id FROM " + ThreadDatabase.TABLE_NAME + ")";
|
||||
|
||||
db.delete(TABLE_NAME, where, new String[] {threadId + ""});
|
||||
db.delete(TABLE_NAME, where, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -34,8 +34,8 @@ import net.sqlcipher.database.SQLiteDatabase;
|
|||
import org.jsoup.helper.StringUtil;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
|
||||
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
|
@ -76,6 +76,9 @@ public class ThreadDatabase extends Database {
|
|||
|
||||
private static final String TAG = ThreadDatabase.class.getSimpleName();
|
||||
|
||||
public static final long NO_TRIM_BEFORE_DATE_SET = 0;
|
||||
public static final int NO_TRIM_MESSAGE_COUNT_SET = Integer.MAX_VALUE;
|
||||
|
||||
public static final String TABLE_NAME = "thread";
|
||||
public static final String ID = "_id";
|
||||
public static final String DATE = "date";
|
||||
|
@ -258,53 +261,88 @@ public class ThreadDatabase extends Database {
|
|||
notifyConversationListListeners();
|
||||
}
|
||||
|
||||
public void trimAllThreads(int length, ProgressListener listener) {
|
||||
Cursor cursor = null;
|
||||
int threadCount = 0;
|
||||
int complete = 0;
|
||||
public void trimAllThreads(int length, long trimBeforeDate) {
|
||||
if (length == NO_TRIM_MESSAGE_COUNT_SET && trimBeforeDate == NO_TRIM_BEFORE_DATE_SET) {
|
||||
return;
|
||||
}
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
|
||||
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
|
||||
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] { ID }, null, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
trimThreadInternal(CursorUtil.requireLong(cursor, ID), length, trimBeforeDate);
|
||||
}
|
||||
}
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
try {
|
||||
cursor = this.getConversationList();
|
||||
|
||||
if (cursor != null)
|
||||
threadCount = cursor.getCount();
|
||||
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
|
||||
trimThread(threadId, length);
|
||||
|
||||
listener.onProgress(++complete, threadCount);
|
||||
}
|
||||
mmsSmsDatabase.deleteAbandonedMessages();
|
||||
attachmentDatabase.trimAllAbandonedAttachments();
|
||||
groupReceiptDatabase.deleteAbandonedRows();
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
attachmentDatabase.deleteAbandonedAttachmentFiles();
|
||||
|
||||
notifyAttachmentListeners();
|
||||
notifyStickerListeners();
|
||||
notifyStickerPackListeners();
|
||||
}
|
||||
|
||||
public void trimThread(long threadId, int length) {
|
||||
Log.i(TAG, "Trimming thread: " + threadId + " to: " + length);
|
||||
Cursor cursor = null;
|
||||
public void trimThread(long threadId, int length, long trimBeforeDate) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context);
|
||||
GroupReceiptDatabase groupReceiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context);
|
||||
MmsSmsDatabase mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(context);
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
try {
|
||||
cursor = DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId);
|
||||
|
||||
if (cursor != null && length > 0 && cursor.getCount() > length) {
|
||||
Log.w(TAG, "Cursor count is greater than length!");
|
||||
cursor.moveToPosition(length - 1);
|
||||
|
||||
long lastTweetDate = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_RECEIVED));
|
||||
|
||||
Log.i(TAG, "Cut off tweet date: " + lastTweetDate);
|
||||
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
|
||||
DatabaseFactory.getMmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, lastTweetDate);
|
||||
|
||||
update(threadId, false);
|
||||
notifyConversationListeners(threadId);
|
||||
}
|
||||
trimThreadInternal(threadId, length, trimBeforeDate);
|
||||
mmsSmsDatabase.deleteAbandonedMessages();
|
||||
attachmentDatabase.trimAllAbandonedAttachments();
|
||||
groupReceiptDatabase.deleteAbandonedRows();
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
if (cursor != null)
|
||||
cursor.close();
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
attachmentDatabase.deleteAbandonedAttachmentFiles();
|
||||
|
||||
notifyAttachmentListeners();
|
||||
notifyStickerListeners();
|
||||
notifyStickerPackListeners();
|
||||
}
|
||||
|
||||
private void trimThreadInternal(long threadId, int length, long trimBeforeDate) {
|
||||
if (length == NO_TRIM_MESSAGE_COUNT_SET && trimBeforeDate == NO_TRIM_BEFORE_DATE_SET) {
|
||||
return;
|
||||
}
|
||||
|
||||
long trimDate = trimBeforeDate;
|
||||
|
||||
if (length != NO_TRIM_MESSAGE_COUNT_SET) {
|
||||
try (Cursor cursor = DatabaseFactory.getMmsSmsDatabase(context).getConversation(threadId)) {
|
||||
if (cursor != null && length > 0 && cursor.getCount() > length) {
|
||||
cursor.moveToPosition(length - 1);
|
||||
trimDate = Math.max(trimDate, cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_RECEIVED)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trimDate != NO_TRIM_BEFORE_DATE_SET) {
|
||||
Log.i(TAG, "Trimming thread: " + threadId + " before: " + trimBeforeDate);
|
||||
|
||||
DatabaseFactory.getMmsSmsDatabase(context).deleteMessagesInThreadBeforeDate(threadId, trimBeforeDate);
|
||||
|
||||
update(threadId, false);
|
||||
notifyConversationListeners(threadId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1153,10 +1191,6 @@ public class ThreadDatabase extends Database {
|
|||
return query;
|
||||
}
|
||||
|
||||
public interface ProgressListener {
|
||||
void onProgress(int complete, int total);
|
||||
}
|
||||
|
||||
public Reader readerFor(Cursor cursor) {
|
||||
return new Reader(cursor);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
|||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
||||
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
||||
import org.thoughtcrime.securesms.service.TrimThreadsByDateManager;
|
||||
import org.thoughtcrime.securesms.util.EarlyMessageCache;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.FrameRateTracker;
|
||||
|
@ -60,6 +61,7 @@ public class ApplicationDependencies {
|
|||
private static GroupsV2Operations groupsV2Operations;
|
||||
private static EarlyMessageCache earlyMessageCache;
|
||||
private static MessageNotifier messageNotifier;
|
||||
private static TrimThreadsByDateManager trimThreadsByDateManager;
|
||||
|
||||
@MainThread
|
||||
public static synchronized void init(@NonNull Application application, @NonNull Provider provider) {
|
||||
|
@ -67,9 +69,10 @@ public class ApplicationDependencies {
|
|||
throw new IllegalStateException("Already initialized!");
|
||||
}
|
||||
|
||||
ApplicationDependencies.application = application;
|
||||
ApplicationDependencies.provider = provider;
|
||||
ApplicationDependencies.messageNotifier = provider.provideMessageNotifier();
|
||||
ApplicationDependencies.application = application;
|
||||
ApplicationDependencies.provider = provider;
|
||||
ApplicationDependencies.messageNotifier = provider.provideMessageNotifier();
|
||||
ApplicationDependencies.trimThreadsByDateManager = provider.provideTrimThreadsByDateManager();
|
||||
}
|
||||
|
||||
public static @NonNull Application getApplication() {
|
||||
|
@ -257,6 +260,11 @@ public class ApplicationDependencies {
|
|||
return incomingMessageObserver;
|
||||
}
|
||||
|
||||
public static synchronized @NonNull TrimThreadsByDateManager getTrimThreadsByDateManager() {
|
||||
assertInitialization();
|
||||
return trimThreadsByDateManager;
|
||||
}
|
||||
|
||||
private static void assertInitialization() {
|
||||
if (application == null || provider == null) {
|
||||
throw new UninitializedException();
|
||||
|
@ -279,6 +287,7 @@ public class ApplicationDependencies {
|
|||
@NonNull EarlyMessageCache provideEarlyMessageCache();
|
||||
@NonNull MessageNotifier provideMessageNotifier();
|
||||
@NonNull IncomingMessageObserver provideIncomingMessageObserver();
|
||||
@NonNull TrimThreadsByDateManager provideTrimThreadsByDateManager();
|
||||
}
|
||||
|
||||
private static class UninitializedException extends IllegalStateException {
|
||||
|
|
|
@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.push.SecurityEventListener;
|
|||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
||||
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
||||
import org.thoughtcrime.securesms.service.TrimThreadsByDateManager;
|
||||
import org.thoughtcrime.securesms.util.AlarmSleepTimer;
|
||||
import org.thoughtcrime.securesms.util.EarlyMessageCache;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
|
@ -178,6 +179,11 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||
return new IncomingMessageObserver(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull TrimThreadsByDateManager provideTrimThreadsByDateManager() {
|
||||
return new TrimThreadsByDateManager(context);
|
||||
}
|
||||
|
||||
private static class DynamicCredentialsProvider implements CredentialsProvider {
|
||||
|
||||
private final Context context;
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.thoughtcrime.securesms.migrations.StickerAdditionMigrationJob;
|
|||
import org.thoughtcrime.securesms.migrations.StickerLaunchMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageCapabilityMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.StorageServiceMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.TrimByLengthSettingsMigrationJob;
|
||||
import org.thoughtcrime.securesms.migrations.UuidMigrationJob;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@ -139,6 +140,7 @@ public final class JobManagerFactories {
|
|||
put(StickerAdditionMigrationJob.KEY, new StickerAdditionMigrationJob.Factory());
|
||||
put(StorageCapabilityMigrationJob.KEY, new StorageCapabilityMigrationJob.Factory());
|
||||
put(StorageServiceMigrationJob.KEY, new StorageServiceMigrationJob.Factory());
|
||||
put(TrimByLengthSettingsMigrationJob.KEY, new TrimByLengthSettingsMigrationJob.Factory());
|
||||
put(UuidMigrationJob.KEY, new UuidMigrationJob.Factory());
|
||||
|
||||
// Dead jobs
|
||||
|
|
|
@ -19,10 +19,12 @@ package org.thoughtcrime.securesms.jobs;
|
|||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class TrimThreadJob extends BaseJob {
|
||||
|
||||
|
@ -55,13 +57,15 @@ public class TrimThreadJob extends BaseJob {
|
|||
|
||||
@Override
|
||||
public void onRun() {
|
||||
boolean trimmingEnabled = TextSecurePreferences.isThreadLengthTrimmingEnabled(context);
|
||||
int threadLengthLimit = TextSecurePreferences.getThreadTrimLength(context);
|
||||
KeepMessagesDuration keepMessagesDuration = SignalStore.settings().getKeepMessagesDuration();
|
||||
|
||||
if (!trimmingEnabled)
|
||||
return;
|
||||
int trimLength = SignalStore.settings().isTrimByLengthEnabled() ? SignalStore.settings().getThreadTrimLength()
|
||||
: ThreadDatabase.NO_TRIM_MESSAGE_COUNT_SET;
|
||||
|
||||
DatabaseFactory.getThreadDatabase(context).trimThread(threadId, threadLengthLimit);
|
||||
long trimBeforeDate = keepMessagesDuration != KeepMessagesDuration.FOREVER ? System.currentTimeMillis() - keepMessagesDuration.getDuration()
|
||||
: ThreadDatabase.NO_TRIM_BEFORE_DATE_SET;
|
||||
|
||||
DatabaseFactory.getThreadDatabase(context).trimThread(threadId, trimLength, trimBeforeDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package org.thoughtcrime.securesms.keyvalue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public enum KeepMessagesDuration {
|
||||
FOREVER(0, R.string.preferences_storage__forever, Long.MAX_VALUE),
|
||||
ONE_YEAR(1, R.string.preferences_storage__one_year, TimeUnit.DAYS.toMillis(365)),
|
||||
SIX_MONTHS(2, R.string.preferences_storage__six_months, TimeUnit.DAYS.toMillis(183)),
|
||||
THIRTY_DAYS(3, R.string.preferences_storage__thirty_days, TimeUnit.DAYS.toMillis(30));
|
||||
|
||||
private final int id;
|
||||
private final int stringResource;
|
||||
private final long duration;
|
||||
|
||||
KeepMessagesDuration(int id, @StringRes int stringResource, long duration) {
|
||||
this.id = id;
|
||||
this.stringResource = stringResource;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public @StringRes int getStringResource() {
|
||||
return stringResource;
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
static @NonNull KeepMessagesDuration fromId(int id) {
|
||||
return values()[id];
|
||||
}
|
||||
}
|
|
@ -4,7 +4,11 @@ import androidx.annotation.NonNull;
|
|||
|
||||
public final class SettingsValues extends SignalStoreValues {
|
||||
|
||||
public static final String LINK_PREVIEWS = "settings.link_previews";
|
||||
public static final String LINK_PREVIEWS = "settings.link_previews";
|
||||
public static final String KEEP_MESSAGES_DURATION = "settings.keep_messages_duration";
|
||||
|
||||
public static final String THREAD_TRIM_LENGTH = "pref_trim_length";
|
||||
public static final String THREAD_TRIM_ENABLED = "pref_trim_threads";
|
||||
|
||||
SettingsValues(@NonNull KeyValueStore store) {
|
||||
super(store);
|
||||
|
@ -24,4 +28,29 @@ public final class SettingsValues extends SignalStoreValues {
|
|||
public void setLinkPreviewsEnabled(boolean enabled) {
|
||||
putBoolean(LINK_PREVIEWS, enabled);
|
||||
}
|
||||
|
||||
public @NonNull KeepMessagesDuration getKeepMessagesDuration() {
|
||||
return KeepMessagesDuration.fromId(getInteger(KEEP_MESSAGES_DURATION, 0));
|
||||
}
|
||||
|
||||
public void setKeepMessagesForDuration(@NonNull KeepMessagesDuration duration) {
|
||||
putInteger(KEEP_MESSAGES_DURATION, duration.getId());
|
||||
}
|
||||
|
||||
public boolean isTrimByLengthEnabled() {
|
||||
return getBoolean(THREAD_TRIM_ENABLED, false);
|
||||
}
|
||||
|
||||
public void setThreadTrimByLengthEnabled(boolean enabled) {
|
||||
putBoolean(THREAD_TRIM_ENABLED, enabled);
|
||||
}
|
||||
|
||||
public int getThreadTrimLength() {
|
||||
return getInteger(THREAD_TRIM_LENGTH, 500);
|
||||
}
|
||||
|
||||
public void setThreadTrimLength(int length) {
|
||||
putInteger(THREAD_TRIM_LENGTH, length);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public class ApplicationMigrations {
|
|||
|
||||
private static final int LEGACY_CANONICAL_VERSION = 455;
|
||||
|
||||
public static final int CURRENT_VERSION = 17;
|
||||
public static final int CURRENT_VERSION = 18;
|
||||
|
||||
private static final class Version {
|
||||
static final int LEGACY = 1;
|
||||
|
@ -59,6 +59,7 @@ public class ApplicationMigrations {
|
|||
static final int PIN_REMINDER = 15;
|
||||
static final int VERSIONED_PROFILE = 16;
|
||||
static final int PIN_OPT_OUT = 17;
|
||||
static final int TRIM_SETTINGS = 18;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -241,6 +242,10 @@ public class ApplicationMigrations {
|
|||
jobs.put(Version.PIN_OPT_OUT, new PinOptOutMigration());
|
||||
}
|
||||
|
||||
if (lastSeenVersion < Version.TRIM_SETTINGS) {
|
||||
jobs.put(Version.TRIM_SETTINGS, new TrimByLengthSettingsMigrationJob());
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
package org.thoughtcrime.securesms.migrations;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import static org.thoughtcrime.securesms.keyvalue.SettingsValues.THREAD_TRIM_ENABLED;
|
||||
import static org.thoughtcrime.securesms.keyvalue.SettingsValues.THREAD_TRIM_LENGTH;
|
||||
|
||||
public class TrimByLengthSettingsMigrationJob extends MigrationJob {
|
||||
|
||||
private static final String TAG = Log.tag(TrimByLengthSettingsMigrationJob.class);
|
||||
|
||||
public static final String KEY = "TrimByLengthSettingsMigrationJob";
|
||||
|
||||
TrimByLengthSettingsMigrationJob() {
|
||||
this(new Parameters.Builder().build());
|
||||
}
|
||||
|
||||
private TrimByLengthSettingsMigrationJob(@NonNull Parameters parameters) {
|
||||
super(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isUiBlocking() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void performMigration() throws Exception {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(ApplicationDependencies.getApplication());
|
||||
if (preferences.contains(THREAD_TRIM_ENABLED)) {
|
||||
SignalStore.settings().setThreadTrimByLengthEnabled(preferences.getBoolean(THREAD_TRIM_ENABLED, false));
|
||||
//noinspection ConstantConditions
|
||||
SignalStore.settings().setThreadTrimLength(Integer.parseInt(preferences.getString(THREAD_TRIM_LENGTH, "500")));
|
||||
|
||||
preferences.edit()
|
||||
.remove(THREAD_TRIM_ENABLED)
|
||||
.remove(THREAD_TRIM_LENGTH)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean shouldRetry(@NonNull Exception e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull String getFactoryKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
public static class Factory implements Job.Factory<TrimByLengthSettingsMigrationJob> {
|
||||
@Override
|
||||
public @NonNull TrimByLengthSettingsMigrationJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
||||
return new TrimByLengthSettingsMigrationJob(parameters);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,35 +1,69 @@
|
|||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.settings.BaseSettingsAdapter;
|
||||
import org.thoughtcrime.securesms.components.settings.BaseSettingsFragment;
|
||||
import org.thoughtcrime.securesms.components.settings.CustomizableSingleSelectSetting;
|
||||
import org.thoughtcrime.securesms.components.settings.SingleSelectSetting;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration;
|
||||
import org.thoughtcrime.securesms.keyvalue.SettingsValues;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.StoragePreferenceCategory;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Trimmer;
|
||||
import org.thoughtcrime.securesms.util.MappingModelList;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
public class StoragePreferenceFragment extends ListSummaryPreferenceFragment {
|
||||
|
||||
private static final String TAG = Log.tag(StoragePreferenceFragment.class);
|
||||
private Preference keepMessages;
|
||||
private Preference trimLength;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle paramBundle) {
|
||||
public void onCreate(@Nullable Bundle paramBundle) {
|
||||
super.onCreate(paramBundle);
|
||||
|
||||
findPreference(TextSecurePreferences.THREAD_TRIM_NOW)
|
||||
.setOnPreferenceClickListener(new TrimNowClickListener());
|
||||
findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH)
|
||||
.setOnPreferenceChangeListener(new TrimLengthValidationListener());
|
||||
findPreference("pref_storage_clear_message_history")
|
||||
.setOnPreferenceClickListener(new ClearMessageHistoryClickListener());
|
||||
|
||||
trimLength = findPreference(SettingsValues.THREAD_TRIM_LENGTH);
|
||||
trimLength.setOnPreferenceClickListener(p -> {
|
||||
getApplicationPreferencesActivity().requireSupportActionBar().setTitle(R.string.preferences__conversation_length_limit);
|
||||
getApplicationPreferencesActivity().pushFragment(BaseSettingsFragment.create(new ConversationLengthLimitConfiguration()));
|
||||
return true;
|
||||
});
|
||||
|
||||
keepMessages = findPreference(SettingsValues.KEEP_MESSAGES_DURATION);
|
||||
keepMessages.setOnPreferenceClickListener(p -> {
|
||||
getApplicationPreferencesActivity().requireSupportActionBar().setTitle(R.string.preferences__keep_messages);
|
||||
getApplicationPreferencesActivity().pushFragment(BaseSettingsFragment.create(new KeepMessagesConfiguration()));
|
||||
return true;
|
||||
});
|
||||
|
||||
StoragePreferenceCategory storageCategory = (StoragePreferenceCategory) findPreference("pref_storage_category");
|
||||
FragmentActivity activity = requireActivity();
|
||||
|
@ -41,19 +75,24 @@ public class StoragePreferenceFragment extends ListSummaryPreferenceFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
|
||||
public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
|
||||
addPreferencesFromResource(R.xml.preferences_storage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((ApplicationPreferencesActivity)getActivity()).getSupportActionBar().setTitle(R.string.preferences__storage);
|
||||
((ApplicationPreferencesActivity) requireActivity()).requireSupportActionBar().setTitle(R.string.preferences__storage);
|
||||
|
||||
FragmentActivity activity = requireActivity();
|
||||
ApplicationPreferencesViewModel viewModel = ApplicationPreferencesViewModel.getApplicationPreferencesViewModel(activity);
|
||||
|
||||
viewModel.refreshStorageBreakdown(activity.getApplicationContext());
|
||||
|
||||
keepMessages.setSummary(SignalStore.settings().getKeepMessagesDuration().getStringResource());
|
||||
|
||||
trimLength.setSummary(SignalStore.settings().isTrimByLengthEnabled() ? getString(R.string.preferences_storage__s_messages, NumberFormat.getInstance().format(SignalStore.settings().getThreadTrimLength()))
|
||||
: getString(R.string.preferences_storage__none));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -61,49 +100,197 @@ public class StoragePreferenceFragment extends ListSummaryPreferenceFragment {
|
|||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
private class TrimNowClickListener implements Preference.OnPreferenceClickListener {
|
||||
private @NonNull ApplicationPreferencesActivity getApplicationPreferencesActivity() {
|
||||
return (ApplicationPreferencesActivity) requireActivity();
|
||||
}
|
||||
|
||||
private class ClearMessageHistoryClickListener implements Preference.OnPreferenceClickListener {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
final int threadLengthLimit = TextSecurePreferences.getThreadTrimLength(getActivity());
|
||||
public boolean onPreferenceClick(@NonNull Preference preference) {
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.ApplicationPreferencesActivity_delete_all_old_messages_now)
|
||||
.setMessage(getResources().getQuantityString(R.plurals.ApplicationPreferencesActivity_this_will_immediately_trim_all_conversations_to_the_d_most_recent_messages,
|
||||
threadLengthLimit, threadLengthLimit))
|
||||
.setPositiveButton(R.string.ApplicationPreferencesActivity_delete, (dialog, which) -> Trimmer.trimAllThreads(getActivity(), threadLengthLimit))
|
||||
.setTitle(R.string.preferences_storage__clear_message_history)
|
||||
.setMessage(R.string.preferences_storage__this_will_delete_all_message_history_and_media_from_your_device)
|
||||
.setPositiveButton(R.string.delete, (d, w) -> showAreYouReallySure())
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showAreYouReallySure() {
|
||||
new AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.preferences_storage__are_you_sure_you_want_to_delete_all_message_history)
|
||||
.setMessage(R.string.preferences_storage__all_message_history_will_be_permanently_removed_this_action_cannot_be_undone)
|
||||
.setPositiveButton(R.string.preferences_storage__delete_all_now, (d, w) -> SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getThreadDatabase(ApplicationDependencies.getApplication()).deleteAllConversations()))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
private class TrimLengthValidationListener implements Preference.OnPreferenceChangeListener {
|
||||
public static class KeepMessagesConfiguration extends BaseSettingsFragment.Configuration implements SingleSelectSetting.SingleSelectSelectionChangedListener {
|
||||
|
||||
TrimLengthValidationListener() {
|
||||
EditTextPreference preference = (EditTextPreference)findPreference(TextSecurePreferences.THREAD_TRIM_LENGTH);
|
||||
onPreferenceChange(preference, preference.getText());
|
||||
@Override
|
||||
public void configureAdapter(@NonNull BaseSettingsAdapter adapter) {
|
||||
adapter.configureSingleSelect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
if (newValue == null || ((String)newValue).trim().length() == 0) {
|
||||
return false;
|
||||
public @NonNull MappingModelList getSettings() {
|
||||
KeepMessagesDuration currentDuration = SignalStore.settings().getKeepMessagesDuration();
|
||||
return Stream.of(KeepMessagesDuration.values())
|
||||
.map(duration -> new SingleSelectSetting.Item(duration, activity.getString(duration.getStringResource()), duration.equals(currentDuration)))
|
||||
.collect(MappingModelList.toMappingModelList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged(@NonNull Object selection) {
|
||||
KeepMessagesDuration currentDuration = SignalStore.settings().getKeepMessagesDuration();
|
||||
KeepMessagesDuration newDuration = (KeepMessagesDuration) selection;
|
||||
|
||||
if (newDuration.ordinal() > currentDuration.ordinal()) {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.preferences_storage__delete_older_messages)
|
||||
.setMessage(activity.getString(R.string.preferences_storage__this_will_permanently_delete_all_message_history_and_media, activity.getString(newDuration.getStringResource())))
|
||||
.setPositiveButton(R.string.delete, (d, w) -> updateTrimByTime(newDuration))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
} else {
|
||||
updateTrimByTime(newDuration);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTrimByTime(@NonNull KeepMessagesDuration newDuration) {
|
||||
SignalStore.settings().setKeepMessagesForDuration(newDuration);
|
||||
updateSettingsList();
|
||||
ApplicationDependencies.getTrimThreadsByDateManager().scheduleIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConversationLengthLimitConfiguration extends BaseSettingsFragment.Configuration implements CustomizableSingleSelectSetting.CustomizableSingleSelectionListener {
|
||||
|
||||
private static final int CUSTOM_LENGTH = -1;
|
||||
|
||||
@Override
|
||||
public void configureAdapter(@NonNull BaseSettingsAdapter adapter) {
|
||||
adapter.configureSingleSelect(this);
|
||||
adapter.configureCustomizableSingleSelect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull MappingModelList getSettings() {
|
||||
int trimLength = SignalStore.settings().isTrimByLengthEnabled() ? SignalStore.settings().getThreadTrimLength() : 0;
|
||||
int[] options = activity.getResources().getIntArray(R.array.conversation_length_limit);
|
||||
boolean hasSelection = false;
|
||||
MappingModelList settings = new MappingModelList();
|
||||
|
||||
for (int option : options) {
|
||||
boolean isSelected = option == trimLength;
|
||||
String text = option == 0 ? activity.getString(R.string.preferences_storage__none)
|
||||
: activity.getString(R.string.preferences_storage__s_messages, NumberFormat.getInstance().format(option));
|
||||
|
||||
settings.add(new SingleSelectSetting.Item(option, text, isSelected));
|
||||
|
||||
hasSelection = hasSelection || isSelected;
|
||||
}
|
||||
|
||||
int value;
|
||||
try {
|
||||
value = Integer.parseInt((String)newValue);
|
||||
} catch (NumberFormatException nfe) {
|
||||
Log.w(TAG, nfe);
|
||||
return false;
|
||||
int currentValue = SignalStore.settings().getThreadTrimLength();
|
||||
settings.add(new CustomizableSingleSelectSetting.Item(CUSTOM_LENGTH,
|
||||
activity.getString(R.string.preferences_storage__custom),
|
||||
!hasSelection,
|
||||
currentValue,
|
||||
activity.getString(R.string.preferences_storage__s_messages, NumberFormat.getInstance().format(currentValue))));
|
||||
return settings;
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@Override
|
||||
public void onCustomizeClicked(@Nullable CustomizableSingleSelectSetting.Item item) {
|
||||
boolean trimLengthEnabled = SignalStore.settings().isTrimByLengthEnabled();
|
||||
int trimLength = trimLengthEnabled ? SignalStore.settings().getThreadTrimLength() : 0;
|
||||
|
||||
View view = LayoutInflater.from(activity).inflate(R.layout.customizable_setting_edit_text, null, false);
|
||||
EditText editText = view.findViewById(R.id.customizable_setting_edit_text);
|
||||
if (trimLength > 0) {
|
||||
editText.setText(String.valueOf(trimLength));
|
||||
}
|
||||
|
||||
if (value < 1) {
|
||||
return false;
|
||||
}
|
||||
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.preferences_Storage__custom_conversation_length_limit)
|
||||
.setView(view)
|
||||
.setPositiveButton(android.R.string.ok, (d, w) -> onSelectionChanged(Integer.parseInt(editText.getText().toString())))
|
||||
.setNegativeButton(android.R.string.cancel, (d, w) -> updateSettingsList())
|
||||
.create();
|
||||
|
||||
preference.setSummary(getResources().getQuantityString(R.plurals.ApplicationPreferencesActivity_messages_per_conversation, value, value));
|
||||
return true;
|
||||
dialog.setOnShowListener(d -> {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(!TextUtils.isEmpty(editText.getText()));
|
||||
editText.requestFocus();
|
||||
editText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(@NonNull Editable sequence) {
|
||||
CharSequence trimmed = StringUtil.trimSequence(sequence);
|
||||
if (TextUtils.isEmpty(trimmed)) {
|
||||
sequence.replace(0, sequence.length(), "");
|
||||
} else {
|
||||
try {
|
||||
Integer.parseInt(trimmed.toString());
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
|
||||
return;
|
||||
} catch (NumberFormatException e) {
|
||||
String onlyDigits = trimmed.toString().replaceAll("[^\\d]", "");
|
||||
if (!onlyDigits.equals(trimmed.toString())) {
|
||||
sequence.replace(0, sequence.length(), onlyDigits);
|
||||
}
|
||||
}
|
||||
}
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(@NonNull CharSequence sequence, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(@NonNull CharSequence sequence, int start, int before, int count) {}
|
||||
});
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelectionChanged(@NonNull Object selection) {
|
||||
boolean trimLengthEnabled = SignalStore.settings().isTrimByLengthEnabled();
|
||||
int trimLength = trimLengthEnabled ? SignalStore.settings().getThreadTrimLength() : 0;
|
||||
int newTrimLength = (Integer) selection;
|
||||
|
||||
if (newTrimLength > 0 && (!trimLengthEnabled || newTrimLength < trimLength)) {
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.preferences_storage__delete_older_messages)
|
||||
.setMessage(activity.getString(R.string.preferences_storage__this_will_permanently_trim_all_conversations_to_the_d_most_recent_messages, NumberFormat.getInstance().format(newTrimLength)))
|
||||
.setPositiveButton(R.string.delete, (d, w) -> updateTrimByLength(newTrimLength))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
} else if (newTrimLength == CUSTOM_LENGTH) {
|
||||
onCustomizeClicked(null);
|
||||
} else {
|
||||
updateTrimByLength(newTrimLength);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTrimByLength(int length) {
|
||||
boolean restrictingChange = !SignalStore.settings().isTrimByLengthEnabled() || length < SignalStore.settings().getThreadTrimLength();
|
||||
|
||||
SignalStore.settings().setThreadTrimByLengthEnabled(length > 0);
|
||||
SignalStore.settings().setThreadTrimLength(length);
|
||||
updateSettingsList();
|
||||
|
||||
if (SignalStore.settings().isTrimByLengthEnabled() && restrictingChange) {
|
||||
KeepMessagesDuration keepMessagesDuration = SignalStore.settings().getKeepMessagesDuration();
|
||||
|
||||
long trimBeforeDate = keepMessagesDuration != KeepMessagesDuration.FOREVER ? System.currentTimeMillis() - keepMessagesDuration.getDuration()
|
||||
: ThreadDatabase.NO_TRIM_BEFORE_DATE_SET;
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> DatabaseFactory.getThreadDatabase(ApplicationDependencies.getApplication()).trimAllThreads(length, trimBeforeDate));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeepMessagesDuration;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
public class TrimThreadsByDateManager extends TimedEventManager<TrimThreadsByDateManager.TrimEvent> {
|
||||
|
||||
private static final String TAG = Log.tag(TrimThreadsByDateManager.class);
|
||||
|
||||
private final ThreadDatabase threadDatabase;
|
||||
private final MmsSmsDatabase mmsSmsDatabase;
|
||||
|
||||
public TrimThreadsByDateManager(@NonNull Application application) {
|
||||
super(application, "TrimThreadsByDateManager");
|
||||
|
||||
threadDatabase = DatabaseFactory.getThreadDatabase(application);
|
||||
mmsSmsDatabase = DatabaseFactory.getMmsSmsDatabase(application);
|
||||
|
||||
scheduleIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable TrimEvent getNextClosestEvent() {
|
||||
KeepMessagesDuration keepMessagesDuration = SignalStore.settings().getKeepMessagesDuration();
|
||||
if (keepMessagesDuration == KeepMessagesDuration.FOREVER) {
|
||||
return null;
|
||||
}
|
||||
|
||||
long trimBeforeDate = System.currentTimeMillis() - keepMessagesDuration.getDuration();
|
||||
|
||||
if (mmsSmsDatabase.getMessageCountBeforeDate(trimBeforeDate) > 0) {
|
||||
Log.i(TAG, "Messages exist before date, trim immediately");
|
||||
return new TrimEvent(0);
|
||||
}
|
||||
|
||||
long timestamp = mmsSmsDatabase.getTimestampForFirstMessageAfterDate(trimBeforeDate);
|
||||
|
||||
if (timestamp == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TrimEvent(Math.max(0, keepMessagesDuration.getDuration() - (System.currentTimeMillis() - timestamp)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeEvent(@NonNull TrimEvent event) {
|
||||
KeepMessagesDuration keepMessagesDuration = SignalStore.settings().getKeepMessagesDuration();
|
||||
|
||||
int trimLength = SignalStore.settings().isTrimByLengthEnabled() ? SignalStore.settings().getThreadTrimLength()
|
||||
: ThreadDatabase.NO_TRIM_MESSAGE_COUNT_SET;
|
||||
|
||||
long trimBeforeDate = keepMessagesDuration != KeepMessagesDuration.FOREVER ? System.currentTimeMillis() - keepMessagesDuration.getDuration()
|
||||
: ThreadDatabase.NO_TRIM_BEFORE_DATE_SET;
|
||||
|
||||
Log.i(TAG, "Trimming all threads with length: " + trimLength + " before: " + trimBeforeDate);
|
||||
threadDatabase.trimAllThreads(trimLength, trimBeforeDate);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getDelayForEvent(@NonNull TrimEvent event) {
|
||||
return event.delay;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scheduleAlarm(@NonNull Application application, long delay) {
|
||||
setAlarm(application, delay, TrimThreadsByDateAlarm.class);
|
||||
}
|
||||
|
||||
public static class TrimThreadsByDateAlarm extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = Log.tag(TrimThreadsByDateAlarm.class);
|
||||
|
||||
@Override
|
||||
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
|
||||
Log.d(TAG, "onReceive()");
|
||||
ApplicationDependencies.getTrimThreadsByDateManager().scheduleIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
public static class TrimEvent {
|
||||
final long delay;
|
||||
|
||||
public TrimEvent(long delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.annimon.stream.Collector;
|
||||
import com.annimon.stream.function.BiConsumer;
|
||||
import com.annimon.stream.function.Function;
|
||||
import com.annimon.stream.function.Supplier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MappingModelList extends ArrayList<MappingModel<?>> {
|
||||
|
||||
public static @NonNull Collector<MappingModel<?>, MappingModelList, MappingModelList> toMappingModelList() {
|
||||
return new Collector<MappingModel<?>, MappingModelList, MappingModelList>() {
|
||||
@Override
|
||||
public @NonNull Supplier<MappingModelList> supplier() {
|
||||
return MappingModelList::new;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull BiConsumer<MappingModelList, MappingModel<?>> accumulator() {
|
||||
return MappingModelList::add;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Function<MappingModelList, MappingModelList> finisher() {
|
||||
return mappingModels -> mappingModels;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -51,8 +51,6 @@ public class TextSecurePreferences {
|
|||
public static final String MMSC_USERNAME_PREF = "pref_apn_mmsc_username";
|
||||
private static final String MMSC_CUSTOM_PASSWORD_PREF = "pref_apn_mmsc_custom_password";
|
||||
public static final String MMSC_PASSWORD_PREF = "pref_apn_mmsc_password";
|
||||
public static final String THREAD_TRIM_LENGTH = "pref_trim_length";
|
||||
public static final String THREAD_TRIM_NOW = "pref_trim_now";
|
||||
public static final String ENABLE_MANUAL_MMS_PREF = "pref_enable_manual_mms";
|
||||
|
||||
private static final String LAST_VERSION_CODE_PREF = "last_version_code";
|
||||
|
@ -74,7 +72,6 @@ public class TextSecurePreferences {
|
|||
private static final String SMS_DELIVERY_REPORT_PREF = "pref_delivery_report_sms";
|
||||
public static final String MMS_USER_AGENT = "pref_mms_user_agent";
|
||||
private static final String MMS_CUSTOM_USER_AGENT = "pref_custom_mms_user_agent";
|
||||
private static final String THREAD_TRIM_ENABLED = "pref_trim_threads";
|
||||
private static final String LOCAL_NUMBER_PREF = "pref_local_number";
|
||||
private static final String LOCAL_UUID_PREF = "pref_local_uuid";
|
||||
private static final String LOCAL_USERNAME_PREF = "pref_local_username";
|
||||
|
@ -1014,14 +1011,6 @@ public class TextSecurePreferences {
|
|||
setStringPreference(context, LED_BLINK_PREF_CUSTOM, pattern);
|
||||
}
|
||||
|
||||
public static boolean isThreadLengthTrimmingEnabled(Context context) {
|
||||
return getBooleanPreference(context, THREAD_TRIM_ENABLED, false);
|
||||
}
|
||||
|
||||
public static int getThreadTrimLength(Context context) {
|
||||
return Integer.parseInt(getStringPreference(context, THREAD_TRIM_LENGTH, "500"));
|
||||
}
|
||||
|
||||
public static boolean isSystemEmojiPreferred(Context context) {
|
||||
return getBooleanPreference(context, SYSTEM_EMOJI_PREF, false);
|
||||
}
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
|
||||
public class Trimmer {
|
||||
|
||||
public static void trimAllThreads(Context context, int threadLengthLimit) {
|
||||
new TrimmingProgressTask(context).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadLengthLimit);
|
||||
}
|
||||
|
||||
private static class TrimmingProgressTask extends AsyncTask<Integer, Integer, Void> implements ThreadDatabase.ProgressListener {
|
||||
private ProgressDialog progressDialog;
|
||||
private Context context;
|
||||
|
||||
public TrimmingProgressTask(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
progressDialog = new ProgressDialog(context);
|
||||
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
progressDialog.setCancelable(false);
|
||||
progressDialog.setIndeterminate(false);
|
||||
progressDialog.setTitle(R.string.trimmer__deleting);
|
||||
progressDialog.setMessage(context.getString(R.string.trimmer__deleting_old_messages));
|
||||
progressDialog.setMax(100);
|
||||
progressDialog.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Integer... params) {
|
||||
DatabaseFactory.getThreadDatabase(context).trimAllThreads(params[0], this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Integer... progress) {
|
||||
double count = progress[1];
|
||||
double index = progress[0];
|
||||
|
||||
progressDialog.setProgress((int)Math.round((index / count) * 100.0));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
progressDialog.dismiss();
|
||||
Toast.makeText(context,
|
||||
R.string.trimmer__old_messages_successfully_deleted,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(int complete, int total) {
|
||||
this.publishProgress(complete, total);
|
||||
}
|
||||
}
|
||||
}
|
12
app/src/main/res/drawable/ic_settings_outline_24.xml
Normal file
12
app/src/main/res/drawable/ic_settings_outline_24.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,8.5A3.5,3.5 0,1 1,8.5 12,3.5 3.5,0 0,1 12,8.5M12,7a5,5 0,1 0,5 5,5 5,0 0,0 -5,-5Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,2.51a9.83,9.83 0,0 1,1.21 0.08l0.21,2.49 0.91,0.33a5.72,5.72 0,0 1,0.68 0.28l0.88,0.42L17.8,4.49A9.23,9.23 0,0 1,19.51 6.2L17.89,8.11l0.42,0.88a5.72,5.72 0,0 1,0.28 0.68l0.33,0.91 2.49,0.21a8.91,8.91 0,0 1,0 2.42l-2.49,0.21 -0.33,0.91a5.72,5.72 0,0 1,-0.28 0.68l-0.42,0.88 1.62,1.91a9.23,9.23 0,0 1,-1.71 1.71l-1.91,-1.62 -0.88,0.42a5.72,5.72 0,0 1,-0.68 0.28l-0.91,0.33 -0.21,2.49a9.19,9.19 0,0 1,-2.42 0l-0.21,-2.49 -0.91,-0.33A5.72,5.72 0,0 1,9 18.31l-0.88,-0.42L6.2,19.51A9.23,9.23 0,0 1,4.49 17.8l1.62,-1.91L5.69,15a5.72,5.72 0,0 1,-0.28 -0.68l-0.33,-0.91 -2.49,-0.21a8.91,8.91 0,0 1,0 -2.42l2.49,-0.21 0.33,-0.91A5.72,5.72 0,0 1,5.69 9l0.42,-0.88L4.49,6.2A9.23,9.23 0,0 1,6.2 4.49L8.11,6.11 9,5.69a5.72,5.72 0,0 1,0.68 -0.28l0.91,-0.33 0.21,-2.49A9.83,9.83 0,0 1,12 2.51h0M12,1a10.93,10.93 0,0 0,-1.88 0.16,1 1,0 0,0 -0.79,0.9L9.17,4a7.64,7.64 0,0 0,-0.83 0.35L6.87,3.09a1,1 0,0 0,-0.66 -0.24A1,1 0,0 0,5.67 3,11 11,0 0,0 3.05,5.62a1,1 0,0 0,0 1.25L4.34,8.34A7.64,7.64 0,0 0,4 9.17l-1.92,0.16a1,1 0,0 0,-0.9 0.79,11 11,0 0,0 0,3.76 1,1 0,0 0,0.9 0.79L4,14.83a7.64,7.64 0,0 0,0.35 0.83L3.09,17.13A1,1 0,0 0,3 18.33,11 11,0 0,0 5.62,21a1,1 0,0 0,0.61 0.19,1 1,0 0,0 0.64,-0.23l1.47,-1.25a7.64,7.64 0,0 0,0.83 0.35l0.16,1.92a1,1 0,0 0,0.79 0.9A11.83,11.83 0,0 0,12 23a10.93,10.93 0,0 0,1.88 -0.16,1 1,0 0,0 0.79,-0.9L14.83,20a7.64,7.64 0,0 0,0.83 -0.35l1.47,1.25a1,1 0,0 0,0.66 0.24,1 1,0 0,0 0.54,-0.16A11,11 0,0 0,21 18.38a1,1 0,0 0,0 -1.25l-1.25,-1.47a7.64,7.64 0,0 0,0.35 -0.83l1.92,-0.16a1,1 0,0 0,0.9 -0.79,11 11,0 0,0 0,-3.76 1,1 0,0 0,-0.9 -0.79L20,9.17a7.64,7.64 0,0 0,-0.35 -0.83l1.25,-1.47A1,1 0,0 0,21 5.67a11,11 0,0 0,-2.61 -2.62,1 1,0 0,0 -0.61,-0.19 1,1 0,0 0,-0.64 0.23L15.66,4.34A7.64,7.64 0,0 0,14.83 4l-0.16,-1.92a1,1 0,0 0,-0.79 -0.9A11.83,11.83 0,0 0,12 1Z"/>
|
||||
</vector>
|
9
app/src/main/res/layout/base_settings_fragment.xml
Normal file
9
app/src/main/res/layout/base_settings_fragment.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/base_settings_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollIndicators="top|bottom"
|
||||
tools:ignore="UnusedAttribute"
|
||||
tools:listitem="@layout/single_select_item" />
|
14
app/src/main/res/layout/customizable_setting_edit_text.xml
Normal file
14
app/src/main/res/layout/customizable_setting_edit_text.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/customizable_setting_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"/>
|
||||
|
||||
</FrameLayout>
|
77
app/src/main/res/layout/customizable_single_select_item.xml
Normal file
77
app/src/main/res/layout/customizable_single_select_item.xml
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="?attr/dialogPreferredPadding"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/customizable_single_select_radio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingEnd="20dp"
|
||||
android:clickable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="RtlSymmetry" />
|
||||
|
||||
<CheckedTextView
|
||||
android:id="@+id/single_select_item_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?attr/textColorAlertDialogListItem"
|
||||
app:layout_constraintBottom_toTopOf="@id/customizable_single_select_summary"
|
||||
app:layout_constraintStart_toEndOf="@id/customizable_single_select_radio"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Pick me!" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/customizable_single_select_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/core_grey_60"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/single_select_item_text"
|
||||
app:layout_constraintTop_toBottomOf="@id/single_select_item_text"
|
||||
tools:text="Test" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/customizable_single_select_customize"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:contentDescription="@string/configurable_single_select__customize_option"
|
||||
android:padding="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_settings_outline_24" />
|
||||
|
||||
<View
|
||||
android:id="@+id/customizable_single_select_divider"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="10dp"
|
||||
android:background="@color/core_grey_20"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/customizable_single_select_customize"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/customizable_single_select_customize_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="customizable_single_select_summary,customizable_single_select_customize,customizable_single_select_divider" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
16
app/src/main/res/layout/single_select_item.xml
Normal file
16
app/src/main/res/layout/single_select_item.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/single_select_item_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:drawableStart="?android:attr/listChoiceIndicatorSingle"
|
||||
android:drawablePadding="20dp"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="?attr/dialogPreferredPadding"
|
||||
tools:text="Pick me!"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="?attr/textColorAlertDialogListItem" />
|
|
@ -294,6 +294,14 @@
|
|||
<item>604800</item>
|
||||
</integer-array>
|
||||
|
||||
<integer-array name="conversation_length_limit">
|
||||
<item>0</item>
|
||||
<item>5000</item>
|
||||
<item>1000</item>
|
||||
<item>500</item>
|
||||
<item>100</item>
|
||||
</integer-array>
|
||||
|
||||
<array name="scribble_colors">
|
||||
<item>#ffffff</item>
|
||||
<item>#ff0000</item>
|
||||
|
|
|
@ -2059,13 +2059,11 @@
|
|||
<string name="preferences__mmsc_password">MMSC Password</string>
|
||||
<string name="preferences__sms_delivery_reports">SMS delivery reports</string>
|
||||
<string name="preferences__request_a_delivery_report_for_each_sms_message_you_send">Request a delivery report for each SMS message you send</string>
|
||||
<string name="preferences__automatically_delete_older_messages_once_a_conversation_exceeds_a_specified_length">Automatically delete older messages once a conversation exceeds a specified length</string>
|
||||
<string name="preferences__delete_old_messages">Delete old messages</string>
|
||||
<string name="preferences__chats">Chats and media</string>
|
||||
<string name="preferences__storage">Storage</string>
|
||||
<string name="preferences__conversation_length_limit">Conversation length limit</string>
|
||||
<string name="preferences__trim_all_conversations_now">Trim all conversations now</string>
|
||||
<string name="preferences__scan_through_all_conversations_and_enforce_conversation_length_limits">Scan through all conversations and enforce conversation length limits</string>
|
||||
<string name="preferences__keep_messages">Keep messages</string>
|
||||
<string name="preferences__clear_message_history">Clear message history</string>
|
||||
<string name="preferences__linked_devices">Linked devices</string>
|
||||
<string name="preferences__light_theme">Light</string>
|
||||
<string name="preferences__dark_theme">Dark</string>
|
||||
|
@ -2095,13 +2093,29 @@
|
|||
<string name="preferences_chats__when_using_wifi">When using Wi-Fi</string>
|
||||
<string name="preferences_chats__when_roaming">When roaming</string>
|
||||
<string name="preferences_chats__media_auto_download">Media auto-download</string>
|
||||
<string name="preferences_chats__message_trimming">Message trimming</string>
|
||||
<string name="preferences_chats__message_history">Message history</string>
|
||||
<string name="preferences_storage__storage_usage">Storage usage</string>
|
||||
<string name="preferences_storage__photos">Photos</string>
|
||||
<string name="preferences_storage__videos">Videos</string>
|
||||
<string name="preferences_storage__files">Files</string>
|
||||
<string name="preferences_storage__audio">Audio</string>
|
||||
<string name="preferences_storage__review_storage">Review storage</string>
|
||||
<string name="preferences_storage__delete_older_messages">Delete older messages?</string>
|
||||
<string name="preferences_storage__clear_message_history">Clear message history?</string>
|
||||
<string name="preferences_storage__this_will_permanently_delete_all_message_history_and_media">This will permanently delete all messsage history and media from your device that are older than %1$s.</string>
|
||||
<string name="preferences_storage__this_will_permanently_trim_all_conversations_to_the_d_most_recent_messages">This will permanently trim all conversations to the %1$s most recent messages.</string>
|
||||
<string name="preferences_storage__this_will_delete_all_message_history_and_media_from_your_device">This will permanently delete all messsage history and media from your device.</string>
|
||||
<string name="preferences_storage__are_you_sure_you_want_to_delete_all_message_history">Are you sure you want to delete all message history?</string>
|
||||
<string name="preferences_storage__all_message_history_will_be_permanently_removed_this_action_cannot_be_undone">All message history will be permanently removed. This action cannot be undone.</string>
|
||||
<string name="preferences_storage__delete_all_now">Delete all now</string>
|
||||
<string name="preferences_storage__forever">Forever</string>
|
||||
<string name="preferences_storage__one_year">1 year</string>
|
||||
<string name="preferences_storage__six_months">6 months</string>
|
||||
<string name="preferences_storage__thirty_days">30 days</string>
|
||||
<string name="preferences_storage__none">None</string>
|
||||
<string name="preferences_storage__s_messages">%1$s messages</string>
|
||||
<string name="preferences_storage__custom">Custom</string>
|
||||
<string name="preferences_Storage__custom_conversation_length_limit">Custom conversation length limit</string>
|
||||
<string name="preferences_advanced__use_system_emoji">Use system emoji</string>
|
||||
<string name="preferences_advanced__disable_signal_built_in_emoji_support">Disable Signal\'s built-in emoji support</string>
|
||||
<string name="preferences_advanced__relay_all_calls_through_the_signal_server_to_avoid_revealing_your_ip_address">Relay all calls through the Signal server to avoid revealing your IP address to your contact. Enabling will reduce call quality.</string>
|
||||
|
@ -2132,6 +2146,8 @@
|
|||
<string name="preferences_notifications__receive_notifications_when_youre_mentioned_in_muted_chats">Receive notifications when you’re mentioned in muted chats</string>
|
||||
<string name="preferences_setup_a_username">Setup a username</string>
|
||||
|
||||
<string name="configurable_single_select__customize_option">Customize option</string>
|
||||
|
||||
<!-- Internal only preferences -->
|
||||
<string name="preferences__internal_preferences" translatable="false">Internal Preferences</string>
|
||||
<string name="preferences__internal_preferences_groups_v2" translatable="false">Groups V2</string>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<PreferenceCategory android:title="@string/preferences_storage__storage_usage" />
|
||||
|
||||
|
@ -9,26 +10,22 @@
|
|||
|
||||
<PreferenceCategory
|
||||
android:key="storage_limits"
|
||||
android:title="@string/preferences_chats__message_trimming">
|
||||
|
||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_trim_threads"
|
||||
android:summary="@string/preferences__automatically_delete_older_messages_once_a_conversation_exceeds_a_specified_length"
|
||||
android:title="@string/preferences__delete_old_messages" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="500"
|
||||
android:dependency="pref_trim_threads"
|
||||
android:inputType="number"
|
||||
android:key="pref_trim_length"
|
||||
android:title="@string/preferences__conversation_length_limit" />
|
||||
android:title="@string/preferences_chats__message_history">
|
||||
|
||||
<Preference
|
||||
android:dependency="pref_trim_threads"
|
||||
android:key="pref_trim_now"
|
||||
android:summary="@string/preferences__scan_through_all_conversations_and_enforce_conversation_length_limits"
|
||||
android:title="@string/preferences__trim_all_conversations_now" />
|
||||
android:key="settings.keep_messages_duration"
|
||||
android:title="@string/preferences__keep_messages"
|
||||
tools:summary="@string/preferences_storage__forever" />
|
||||
|
||||
<Preference
|
||||
android:inputType="number"
|
||||
android:key="pref_trim_length"
|
||||
android:title="@string/preferences__conversation_length_limit"
|
||||
tools:summary="None" />
|
||||
|
||||
<Preference
|
||||
android:key="pref_storage_clear_message_history"
|
||||
android:title="@string/preferences__clear_message_history" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue