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