1package eu.siacs.conversations;
  2
  3import android.content.Context;
  4import android.content.SharedPreferences;
  5import android.net.Uri;
  6import android.os.Environment;
  7import androidx.annotation.BoolRes;
  8import androidx.annotation.IntegerRes;
  9import androidx.annotation.NonNull;
 10import androidx.preference.PreferenceManager;
 11import com.google.common.base.Joiner;
 12import com.google.common.base.Splitter;
 13import com.google.common.base.Strings;
 14import eu.siacs.conversations.persistance.FileBackend;
 15import eu.siacs.conversations.services.QuickConversationsService;
 16import eu.siacs.conversations.utils.Compatibility;
 17import java.security.SecureRandom;
 18import java.util.Optional;
 19
 20public class AppSettings {
 21
 22    public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service";
 23    public static final String AWAY_WHEN_SCREEN_IS_OFF = "away_when_screen_off";
 24    public static final String TREAT_VIBRATE_AS_SILENT = "treat_vibrate_as_silent";
 25    public static final String DND_ON_SILENT_MODE = "dnd_on_silent_mode";
 26    public static final String MANUALLY_CHANGE_PRESENCE = "manually_change_presence";
 27    public static final String BLIND_TRUST_BEFORE_VERIFICATION = "btbv";
 28    public static final String AUTOMATIC_MESSAGE_DELETION = "automatic_message_deletion";
 29    public static final String BROADCAST_LAST_ACTIVITY = "last_activity";
 30    public static final String SEND_CHAT_STATES = "chat_states";
 31    public static final String THEME = "theme";
 32    public static final String DYNAMIC_COLORS = "dynamic_colors";
 33    public static final String SHOW_DYNAMIC_TAGS = "show_dynamic_tags";
 34    public static final String OMEMO = "omemo";
 35    public static final String ALLOW_SCREENSHOTS = "allow_screenshots";
 36    public static final String RINGTONE = "call_ringtone";
 37
 38    public static final String CONFIRM_MESSAGES = "confirm_messages";
 39    public static final String ALLOW_MESSAGE_CORRECTION = "allow_message_correction";
 40
 41    public static final String TRUST_SYSTEM_CA_STORE = "trust_system_ca_store";
 42    public static final String REQUIRE_CHANNEL_BINDING = "channel_binding_required";
 43    public static final String NOTIFICATION_RINGTONE = "notification_ringtone";
 44    public static final String NOTIFICATION_HEADS_UP = "notification_headsup";
 45    public static final String NOTIFICATION_VIBRATE = "vibrate_on_notification";
 46    public static final String NOTIFICATION_LED = "led";
 47    public static final String SHOW_CONNECTION_OPTIONS = "show_connection_options";
 48    public static final String USE_TOR = "use_tor";
 49    public static final String CHANNEL_DISCOVERY_METHOD = "channel_discovery_method";
 50    public static final String SEND_CRASH_REPORTS = "send_crash_reports";
 51    public static final String COLORFUL_CHAT_BUBBLES = "use_green_background";
 52    public static final String LARGE_FONT = "large_font";
 53    public static final String SHOW_LINK_PREVIEWS = "show_link_previews";
 54    public static final String SHOW_AVATARS = "show_avatars";
 55    public static final String CALL_INTEGRATION = "call_integration";
 56    public static final String ALIGN_START = "align_start";
 57    public static final String BACKUP_LOCATION = "backup_location";
 58    public static final String AUTO_ACCEPT_FILE_SIZE = "auto_accept_file_size";
 59
 60    private static final String ACCEPT_INVITES_FROM_STRANGERS = "accept_invites_from_strangers";
 61    private static final String NOTIFICATIONS_FROM_STRANGERS = "notifications_from_strangers";
 62    private static final String INSTALLATION_ID = "im.conversations.android.install_id";
 63
 64    private static final String EXTERNAL_STORAGE_AUTHORITY =
 65            "com.android.externalstorage.documents";
 66
 67    private final Context context;
 68
 69    public AppSettings(final Context context) {
 70        this.context = context;
 71    }
 72
 73    public Uri getRingtone() {
 74        final SharedPreferences sharedPreferences =
 75                PreferenceManager.getDefaultSharedPreferences(context);
 76        final String incomingCallRingtone =
 77                sharedPreferences.getString(
 78                        RINGTONE, context.getString(R.string.incoming_call_ringtone));
 79        return Strings.isNullOrEmpty(incomingCallRingtone) ? null : Uri.parse(incomingCallRingtone);
 80    }
 81
 82    public void setRingtone(final Uri uri) {
 83        final SharedPreferences sharedPreferences =
 84                PreferenceManager.getDefaultSharedPreferences(context);
 85        sharedPreferences.edit().putString(RINGTONE, uri == null ? null : uri.toString()).apply();
 86    }
 87
 88    public Uri getNotificationTone() {
 89        final SharedPreferences sharedPreferences =
 90                PreferenceManager.getDefaultSharedPreferences(context);
 91        final String incomingCallRingtone =
 92                sharedPreferences.getString(
 93                        NOTIFICATION_RINGTONE, context.getString(R.string.notification_ringtone));
 94        return Strings.isNullOrEmpty(incomingCallRingtone) ? null : Uri.parse(incomingCallRingtone);
 95    }
 96
 97    public void setNotificationTone(final Uri uri) {
 98        final SharedPreferences sharedPreferences =
 99                PreferenceManager.getDefaultSharedPreferences(context);
100        sharedPreferences
101                .edit()
102                .putString(NOTIFICATION_RINGTONE, uri == null ? null : uri.toString())
103                .apply();
104    }
105
106    public boolean isBTBVEnabled() {
107        return getBooleanPreference(BLIND_TRUST_BEFORE_VERIFICATION, R.bool.btbv);
108    }
109
110    public boolean isTrustSystemCAStore() {
111        return getBooleanPreference(TRUST_SYSTEM_CA_STORE, R.bool.trust_system_ca_store);
112    }
113
114    public boolean isAllowScreenshots() {
115        return getBooleanPreference(ALLOW_SCREENSHOTS, R.bool.allow_screenshots);
116    }
117
118    public boolean isColorfulChatBubbles() {
119        return getBooleanPreference(COLORFUL_CHAT_BUBBLES, R.bool.use_green_background);
120    }
121
122    public boolean isLargeFont() {
123        return getBooleanPreference(LARGE_FONT, R.bool.large_font);
124    }
125
126    public boolean showLinkPreviews() {
127        return getBooleanPreference(SHOW_LINK_PREVIEWS, R.bool.show_link_previews);
128    }
129
130    public boolean isShowAvatars() {
131        return getBooleanPreference(SHOW_AVATARS, R.bool.show_avatars);
132    }
133
134    public boolean isCallIntegration() {
135        return getBooleanPreference(CALL_INTEGRATION, R.bool.call_integration);
136    }
137
138    public boolean isAlignStart() {
139        return getBooleanPreference(ALIGN_START, R.bool.align_start);
140    }
141
142    public boolean isConfirmMessages() {
143        return getBooleanPreference(CONFIRM_MESSAGES, R.bool.confirm_messages);
144    }
145
146    public boolean isAllowMessageCorrection() {
147        return getBooleanPreference(ALLOW_MESSAGE_CORRECTION, R.bool.allow_message_correction);
148    }
149
150    public boolean isBroadcastLastActivity() {
151        return getBooleanPreference(BROADCAST_LAST_ACTIVITY, R.bool.last_activity);
152    }
153
154    public boolean isUserManagedAvailability() {
155        return getBooleanPreference(MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence);
156    }
157
158    public boolean isAutomaticAvailability() {
159        return !isUserManagedAvailability();
160    }
161
162    public boolean isDndOnSilentMode() {
163        return getBooleanPreference(AppSettings.DND_ON_SILENT_MODE, R.bool.dnd_on_silent_mode);
164    }
165
166    public boolean isTreatVibrateAsSilent() {
167        return getBooleanPreference(
168                AppSettings.TREAT_VIBRATE_AS_SILENT, R.bool.treat_vibrate_as_silent);
169    }
170
171    public boolean isAwayWhenScreenLocked() {
172        return getBooleanPreference(
173                AppSettings.AWAY_WHEN_SCREEN_IS_OFF, R.bool.away_when_screen_off);
174    }
175
176    public boolean isUseTor() {
177        return QuickConversationsService.isConversations()
178                && getBooleanPreference(USE_TOR, R.bool.use_tor);
179    }
180
181    public boolean isSendChatStates() {
182        return getBooleanPreference(SEND_CHAT_STATES, R.bool.chat_states);
183    }
184
185    public boolean isExtendedConnectionOptions() {
186        return QuickConversationsService.isConversations()
187                && getBooleanPreference(
188                        AppSettings.SHOW_CONNECTION_OPTIONS, R.bool.show_connection_options);
189    }
190
191    public boolean isAcceptInvitesFromStrangers() {
192        return true;
193    }
194
195    public boolean isNotificationsFromStrangers() {
196        return getBooleanPreference(
197                NOTIFICATIONS_FROM_STRANGERS, R.bool.notifications_from_strangers);
198    }
199
200    public boolean isKeepForegroundService() {
201        return Compatibility.twentySix()
202                || getBooleanPreference(KEEP_FOREGROUND_SERVICE, R.bool.enable_foreground_service);
203    }
204
205    private boolean getBooleanPreference(@NonNull final String name, @BoolRes final int res) {
206        final SharedPreferences sharedPreferences =
207                PreferenceManager.getDefaultSharedPreferences(context);
208        return sharedPreferences.getBoolean(name, context.getResources().getBoolean(res));
209    }
210
211    private long getLongPreference(final String name, @IntegerRes final int res) {
212        final long defaultValue = context.getResources().getInteger(res);
213        final SharedPreferences sharedPreferences =
214                PreferenceManager.getDefaultSharedPreferences(context);
215        try {
216            return Long.parseLong(sharedPreferences.getString(name, String.valueOf(defaultValue)));
217        } catch (final NumberFormatException e) {
218            return defaultValue;
219        }
220    }
221
222    public String getOmemo() {
223        final SharedPreferences sharedPreferences =
224                PreferenceManager.getDefaultSharedPreferences(context);
225        return sharedPreferences.getString(
226                OMEMO, context.getString(R.string.omemo_setting_default));
227    }
228
229    public Uri getBackupLocation() {
230        final SharedPreferences sharedPreferences =
231                PreferenceManager.getDefaultSharedPreferences(context);
232        final String location = sharedPreferences.getString(BACKUP_LOCATION, null);
233        if (Strings.isNullOrEmpty(location)) {
234            final var directory = FileBackend.getBackupDirectory(context);
235            return Uri.fromFile(directory);
236        }
237        return Uri.parse(location);
238    }
239
240    public String getBackupLocationAsPath() {
241        return asPath(getBackupLocation());
242    }
243
244    public static String asPath(final Uri uri) {
245        final var scheme = uri.getScheme();
246        final var path = uri.getPath();
247        if (path == null) {
248            return uri.toString();
249        }
250        if ("file".equalsIgnoreCase(scheme)) {
251            return path;
252        } else if ("content".equalsIgnoreCase(scheme)) {
253            if (EXTERNAL_STORAGE_AUTHORITY.equalsIgnoreCase(uri.getAuthority())) {
254                final var parts = Splitter.on(':').limit(2).splitToList(path);
255                if (parts.size() == 2 && "/tree/primary".equals(parts.get(0))) {
256                    return Joiner.on('/')
257                            .join(Environment.getExternalStorageDirectory(), parts.get(1));
258                }
259            }
260        }
261        return uri.toString();
262    }
263
264    public void setBackupLocation(final Uri uri) {
265        final SharedPreferences sharedPreferences =
266                PreferenceManager.getDefaultSharedPreferences(context);
267        sharedPreferences
268                .edit()
269                .putString(BACKUP_LOCATION, uri == null ? "" : uri.toString())
270                .apply();
271    }
272
273    public boolean isSendCrashReports() {
274        return getBooleanPreference(SEND_CRASH_REPORTS, R.bool.send_crash_reports);
275    }
276
277    public void setSendCrashReports(boolean value) {
278        final SharedPreferences sharedPreferences =
279                PreferenceManager.getDefaultSharedPreferences(context);
280        sharedPreferences.edit().putBoolean(SEND_CRASH_REPORTS, value).apply();
281    }
282
283    public boolean isRequireChannelBinding() {
284        return getBooleanPreference(REQUIRE_CHANNEL_BINDING, R.bool.require_channel_binding);
285    }
286
287    public synchronized long getInstallationId() {
288        final var sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
289        final long existing = sharedPreferences.getLong(INSTALLATION_ID, 0);
290        if (existing != 0) {
291            return existing;
292        }
293        final var secureRandom = new SecureRandom();
294        final var installationId = secureRandom.nextLong();
295        sharedPreferences.edit().putLong(INSTALLATION_ID, installationId).apply();
296        return installationId;
297    }
298
299    public Optional<Long> getAutoAcceptFileSize() {
300        final long autoAcceptFileSize =
301                getLongPreference(AUTO_ACCEPT_FILE_SIZE, R.integer.auto_accept_filesize);
302        return autoAcceptFileSize <= 0 ? Optional.empty() : Optional.of(autoAcceptFileSize);
303    }
304
305    public synchronized void resetInstallationId() {
306        final var secureRandom = new SecureRandom();
307        final var installationId = secureRandom.nextLong();
308        PreferenceManager.getDefaultSharedPreferences(context)
309                .edit()
310                .putLong(INSTALLATION_ID, installationId)
311                .apply();
312    }
313}