1package eu.siacs.conversations.ui;
2
3import android.preference.CheckBoxPreference;
4import android.support.annotation.NonNull;
5import android.support.v7.app.AlertDialog;
6import android.app.FragmentManager;
7import android.content.DialogInterface;
8import android.content.Intent;
9import android.content.SharedPreferences;
10import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
11import android.content.pm.PackageManager;
12
13import android.net.Uri;
14import android.os.Build;
15import android.os.Bundle;
16import android.preference.ListPreference;
17import android.preference.Preference;
18import android.preference.PreferenceCategory;
19import android.preference.PreferenceManager;
20import android.preference.PreferenceScreen;
21import android.provider.MediaStore;
22import android.util.Log;
23import android.view.View;
24import android.view.ViewGroup;
25import android.widget.Toast;
26
27import java.io.File;
28import java.security.KeyStoreException;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collections;
32import java.util.List;
33
34import eu.siacs.conversations.Config;
35import eu.siacs.conversations.R;
36import eu.siacs.conversations.crypto.OmemoSetting;
37import eu.siacs.conversations.entities.Account;
38import eu.siacs.conversations.services.ExportLogsService;
39import eu.siacs.conversations.services.MemorizingTrustManager;
40import eu.siacs.conversations.ui.util.Color;
41import eu.siacs.conversations.utils.GeoHelper;
42import eu.siacs.conversations.utils.TimeframeUtils;
43import rocks.xmpp.addr.Jid;
44
45public class SettingsActivity extends XmppActivity implements
46 OnSharedPreferenceChangeListener {
47
48 public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service";
49 public static final String AWAY_WHEN_SCREEN_IS_OFF = "away_when_screen_off";
50 public static final String TREAT_VIBRATE_AS_SILENT = "treat_vibrate_as_silent";
51 public static final String DND_ON_SILENT_MODE = "dnd_on_silent_mode";
52 public static final String MANUALLY_CHANGE_PRESENCE = "manually_change_presence";
53 public static final String BLIND_TRUST_BEFORE_VERIFICATION = "btbv";
54 public static final String AUTOMATIC_MESSAGE_DELETION = "automatic_message_deletion";
55 public static final String BROADCAST_LAST_ACTIVITY = "last_activity";
56 public static final String THEME = "theme";
57 public static final String SHOW_DYNAMIC_TAGS = "show_dynamic_tags";
58 public static final String OMEMO_SETTING = "omemo";
59
60 public static final int REQUEST_WRITE_LOGS = 0xbf8701;
61 private SettingsFragment mSettingsFragment;
62
63 @Override
64 protected void onCreate(Bundle savedInstanceState) {
65 super.onCreate(savedInstanceState);
66 setContentView(R.layout.activity_settings);
67 FragmentManager fm = getFragmentManager();
68 mSettingsFragment = (SettingsFragment) fm.findFragmentById(R.id.settings_content);
69 if (mSettingsFragment == null || !mSettingsFragment.getClass().equals(SettingsFragment.class)) {
70 mSettingsFragment = new SettingsFragment();
71 fm.beginTransaction().replace(R.id.settings_content, mSettingsFragment).commit();
72 }
73 mSettingsFragment.setActivityIntent(getIntent());
74 this.mTheme = findTheme();
75 setTheme(this.mTheme);
76 getWindow().getDecorView().setBackgroundColor(Color.get(this, R.attr.color_background_primary));
77 setSupportActionBar(findViewById(R.id.toolbar));
78 configureActionBar(getSupportActionBar());
79 }
80
81 @Override
82 void onBackendConnected() {
83
84 }
85
86 @Override
87 public void onStart() {
88 super.onStart();
89 PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
90
91 changeOmemoSettingSummary();
92
93 if (Config.FORCE_ORBOT) {
94 PreferenceCategory connectionOptions = (PreferenceCategory) mSettingsFragment.findPreference("connection_options");
95 PreferenceScreen expert = (PreferenceScreen) mSettingsFragment.findPreference("expert");
96 if (connectionOptions != null) {
97 expert.removePreference(connectionOptions);
98 }
99 }
100
101 PreferenceScreen mainPreferenceScreen = (PreferenceScreen) mSettingsFragment.findPreference("main_screen");
102
103 PreferenceCategory attachmentsCategory = (PreferenceCategory) mSettingsFragment.findPreference("attachments");
104 CheckBoxPreference locationPlugin = (CheckBoxPreference) mSettingsFragment.findPreference("use_share_location_plugin");
105 if (attachmentsCategory != null && locationPlugin != null) {
106 if (!GeoHelper.isLocationPluginInstalled(this)) {
107 attachmentsCategory.removePreference(locationPlugin);
108 }
109 }
110
111 //this feature is only available on Huawei Android 6.
112 PreferenceScreen huaweiPreferenceScreen = (PreferenceScreen) mSettingsFragment.findPreference("huawei");
113 if (huaweiPreferenceScreen != null) {
114 Intent intent = huaweiPreferenceScreen.getIntent();
115 //remove when Api version is above M (Version 6.0) or if the intent is not callable
116 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M || !isCallable(intent)) {
117 PreferenceCategory generalCategory = (PreferenceCategory) mSettingsFragment.findPreference("general");
118 generalCategory.removePreference(huaweiPreferenceScreen);
119 if (generalCategory.getPreferenceCount() == 0) {
120 if (mainPreferenceScreen != null) {
121 mainPreferenceScreen.removePreference(generalCategory);
122 }
123 }
124 }
125 }
126
127 ListPreference automaticMessageDeletionList = (ListPreference) mSettingsFragment.findPreference(AUTOMATIC_MESSAGE_DELETION);
128 if (automaticMessageDeletionList != null) {
129 final int[] choices = getResources().getIntArray(R.array.automatic_message_deletion_values);
130 CharSequence[] entries = new CharSequence[choices.length];
131 CharSequence[] entryValues = new CharSequence[choices.length];
132 for (int i = 0; i < choices.length; ++i) {
133 entryValues[i] = String.valueOf(choices[i]);
134 if (choices[i] == 0) {
135 entries[i] = getString(R.string.never);
136 } else {
137 entries[i] = TimeframeUtils.resolve(this, 1000L * choices[i]);
138 }
139 }
140 automaticMessageDeletionList.setEntries(entries);
141 automaticMessageDeletionList.setEntryValues(entryValues);
142 }
143
144
145 boolean removeLocation = new Intent("eu.siacs.conversations.location.request").resolveActivity(getPackageManager()) == null;
146 boolean removeVoice = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) == null;
147
148 ListPreference quickAction = (ListPreference) mSettingsFragment.findPreference("quick_action");
149 if (quickAction != null && (removeLocation || removeVoice)) {
150 ArrayList<CharSequence> entries = new ArrayList<>(Arrays.asList(quickAction.getEntries()));
151 ArrayList<CharSequence> entryValues = new ArrayList<>(Arrays.asList(quickAction.getEntryValues()));
152 int index = entryValues.indexOf("location");
153 if (index > 0 && removeLocation) {
154 entries.remove(index);
155 entryValues.remove(index);
156 }
157 index = entryValues.indexOf("voice");
158 if (index > 0 && removeVoice) {
159 entries.remove(index);
160 entryValues.remove(index);
161 }
162 quickAction.setEntries(entries.toArray(new CharSequence[entries.size()]));
163 quickAction.setEntryValues(entryValues.toArray(new CharSequence[entryValues.size()]));
164 }
165
166 final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates");
167 if (removeCertsPreference != null) {
168 removeCertsPreference.setOnPreferenceClickListener(preference -> {
169 final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager();
170 final ArrayList<String> aliases = Collections.list(mtm.getCertificates());
171 if (aliases.size() == 0) {
172 displayToast(getString(R.string.toast_no_trusted_certs));
173 return true;
174 }
175 final ArrayList<Integer> selectedItems = new ArrayList<>();
176 final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this);
177 dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title));
178 dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null,
179 (dialog, indexSelected, isChecked) -> {
180 if (isChecked) {
181 selectedItems.add(indexSelected);
182 } else if (selectedItems.contains(indexSelected)) {
183 selectedItems.remove(Integer.valueOf(indexSelected));
184 }
185 if (selectedItems.size() > 0)
186 ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
187 else {
188 ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
189 }
190 });
191
192 dialogBuilder.setPositiveButton(
193 getResources().getString(R.string.dialog_manage_certs_positivebutton), (dialog, which) -> {
194 int count = selectedItems.size();
195 if (count > 0) {
196 for (int i = 0; i < count; i++) {
197 try {
198 Integer item = Integer.valueOf(selectedItems.get(i).toString());
199 String alias = aliases.get(item);
200 mtm.deleteCertificate(alias);
201 } catch (KeyStoreException e) {
202 e.printStackTrace();
203 displayToast("Error: " + e.getLocalizedMessage());
204 }
205 }
206 if (xmppConnectionServiceBound) {
207 reconnectAccounts();
208 }
209 displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
210 }
211 });
212 dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
213 AlertDialog removeCertsDialog = dialogBuilder.create();
214 removeCertsDialog.show();
215 removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
216 return true;
217 });
218 }
219
220 final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs");
221 if (exportLogsPreference != null) {
222 exportLogsPreference.setOnPreferenceClickListener(preference -> {
223 if (hasStoragePermission(REQUEST_WRITE_LOGS)) {
224 startExport();
225 }
226 return true;
227 });
228 }
229
230 if (Config.ONLY_INTERNAL_STORAGE) {
231 final Preference cleanCachePreference = mSettingsFragment.findPreference("clean_cache");
232 if (cleanCachePreference != null) {
233 cleanCachePreference.setOnPreferenceClickListener(preference -> cleanCache());
234 }
235
236 final Preference cleanPrivateStoragePreference = mSettingsFragment.findPreference("clean_private_storage");
237 if (cleanPrivateStoragePreference != null) {
238 cleanPrivateStoragePreference.setOnPreferenceClickListener(preference -> cleanPrivateStorage());
239 }
240 }
241
242 final Preference deleteOmemoPreference = mSettingsFragment.findPreference("delete_omemo_identities");
243 if (deleteOmemoPreference != null) {
244 deleteOmemoPreference.setOnPreferenceClickListener(preference -> deleteOmemoIdentities());
245 }
246 }
247
248 private void changeOmemoSettingSummary() {
249 ListPreference omemoPreference = (ListPreference) mSettingsFragment.findPreference(OMEMO_SETTING);
250 if (omemoPreference != null) {
251 String value = omemoPreference.getValue();
252 switch (value) {
253 case "always":
254 omemoPreference.setSummary(R.string.pref_omemo_setting_summary_always);
255 break;
256 case "default_on":
257 omemoPreference.setSummary(R.string.pref_omemo_setting_summary_default_on);
258 break;
259 case "default_off":
260 omemoPreference.setSummary(R.string.pref_omemo_setting_summary_default_off);
261 break;
262 }
263 } else {
264 Log.d(Config.LOGTAG,"unable to find preference named "+OMEMO_SETTING);
265 }
266 }
267
268 private boolean isCallable(final Intent i) {
269 return i != null && getPackageManager().queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
270 }
271
272
273 private boolean cleanCache() {
274 Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
275 intent.setData(Uri.parse("package:" + getPackageName()));
276 startActivity(intent);
277 return true;
278 }
279
280 private boolean cleanPrivateStorage() {
281 for(String type : Arrays.asList("Images", "Videos", "Files", "Recordings")) {
282 cleanPrivateFiles(type);
283 }
284 return true;
285 }
286
287 private void cleanPrivateFiles(final String type) {
288 try {
289 File dir = new File(getFilesDir().getAbsolutePath(), "/" + type + "/");
290 File[] array = dir.listFiles();
291 if (array != null) {
292 for (int b = 0; b < array.length; b++) {
293 String name = array[b].getName().toLowerCase();
294 if (name.equals(".nomedia")) {
295 continue;
296 }
297 if (array[b].isFile()) {
298 array[b].delete();
299 }
300 }
301 }
302 } catch (Throwable e) {
303 Log.e("CleanCache", e.toString());
304 }
305 }
306
307 private boolean deleteOmemoIdentities() {
308 AlertDialog.Builder builder = new AlertDialog.Builder(this);
309 builder.setTitle(R.string.pref_delete_omemo_identities);
310 final List<CharSequence> accounts = new ArrayList<>();
311 for (Account account : xmppConnectionService.getAccounts()) {
312 if (account.isEnabled()) {
313 accounts.add(account.getJid().asBareJid().toString());
314 }
315 }
316 final boolean[] checkedItems = new boolean[accounts.size()];
317 builder.setMultiChoiceItems(accounts.toArray(new CharSequence[accounts.size()]), checkedItems, (dialog, which, isChecked) -> {
318 checkedItems[which] = isChecked;
319 final AlertDialog alertDialog = (AlertDialog) dialog;
320 for (boolean item : checkedItems) {
321 if (item) {
322 alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
323 return;
324 }
325 }
326 alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
327 });
328 builder.setNegativeButton(R.string.cancel, null);
329 builder.setPositiveButton(R.string.delete_selected_keys, (dialog, which) -> {
330 for (int i = 0; i < checkedItems.length; ++i) {
331 if (checkedItems[i]) {
332 try {
333 Jid jid = Jid.of(accounts.get(i).toString());
334 Account account = xmppConnectionService.findAccountByJid(jid);
335 if (account != null) {
336 account.getAxolotlService().regenerateKeys(true);
337 }
338 } catch (IllegalArgumentException e) {
339 //
340 }
341
342 }
343 }
344 });
345 AlertDialog dialog = builder.create();
346 dialog.show();
347 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
348 return true;
349 }
350
351 @Override
352 public void onStop() {
353 super.onStop();
354 PreferenceManager.getDefaultSharedPreferences(this)
355 .unregisterOnSharedPreferenceChangeListener(this);
356 }
357
358 @Override
359 public void onSharedPreferenceChanged(SharedPreferences preferences, String name) {
360 final List<String> resendPresence = Arrays.asList(
361 "confirm_messages",
362 DND_ON_SILENT_MODE,
363 AWAY_WHEN_SCREEN_IS_OFF,
364 "allow_message_correction",
365 TREAT_VIBRATE_AS_SILENT,
366 MANUALLY_CHANGE_PRESENCE,
367 BROADCAST_LAST_ACTIVITY);
368 if (name.equals(OMEMO_SETTING)) {
369 OmemoSetting.load(this, preferences);
370 changeOmemoSettingSummary();
371 } else if (name.equals(KEEP_FOREGROUND_SERVICE)) {
372 xmppConnectionService.toggleForegroundService();
373 } else if (resendPresence.contains(name)) {
374 if (xmppConnectionServiceBound) {
375 if (name.equals(AWAY_WHEN_SCREEN_IS_OFF) || name.equals(MANUALLY_CHANGE_PRESENCE)) {
376 xmppConnectionService.toggleScreenEventReceiver();
377 }
378 xmppConnectionService.refreshAllPresences();
379 }
380 } else if (name.equals("dont_trust_system_cas")) {
381 xmppConnectionService.updateMemorizingTrustmanager();
382 reconnectAccounts();
383 } else if (name.equals("use_tor")) {
384 reconnectAccounts();
385 } else if (name.equals(AUTOMATIC_MESSAGE_DELETION)) {
386 xmppConnectionService.expireOldMessages(true);
387 } else if (name.equals(THEME)) {
388 final int theme = findTheme();
389 if (this.mTheme != theme) {
390 recreate();
391 }
392 }
393
394 }
395
396 @Override
397 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
398 if (grantResults.length > 0)
399 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
400 if (requestCode == REQUEST_WRITE_LOGS) {
401 startExport();
402 }
403 } else {
404 Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
405 }
406 }
407
408 private void startExport() {
409 startService(new Intent(getApplicationContext(), ExportLogsService.class));
410 }
411
412 private void displayToast(final String msg) {
413 runOnUiThread(() -> Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show());
414 }
415
416 private void reconnectAccounts() {
417 for (Account account : xmppConnectionService.getAccounts()) {
418 if (account.isEnabled()) {
419 xmppConnectionService.reconnectAccountInBackground(account);
420 }
421 }
422 }
423
424 public void refreshUiReal() {
425 //nothing to do. This Activity doesn't implement any listeners
426 }
427
428}