1package eu.siacs.conversations.ui;
2
3import android.app.AlertDialog;
4import android.app.FragmentManager;
5import android.content.DialogInterface;
6import android.content.Intent;
7import android.content.SharedPreferences;
8import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
9import android.content.pm.PackageManager;
10import android.net.Uri;
11import android.os.Build;
12import android.os.Bundle;
13import android.preference.ListPreference;
14import android.preference.Preference;
15import android.preference.PreferenceCategory;
16import android.preference.PreferenceManager;
17import android.preference.PreferenceScreen;
18import android.util.Log;
19import android.widget.Toast;
20
21import java.io.File;
22import java.security.KeyStoreException;
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.Collections;
26import java.util.List;
27import java.util.Locale;
28
29import de.duenndns.ssl.MemorizingTrustManager;
30import eu.siacs.conversations.Config;
31import eu.siacs.conversations.R;
32import eu.siacs.conversations.entities.Account;
33import eu.siacs.conversations.services.ExportLogsService;
34import eu.siacs.conversations.xmpp.XmppConnection;
35import eu.siacs.conversations.xmpp.jid.InvalidJidException;
36import eu.siacs.conversations.xmpp.jid.Jid;
37
38public class SettingsActivity extends XmppActivity implements
39 OnSharedPreferenceChangeListener {
40
41 public static final int REQUEST_WRITE_LOGS = 0xbf8701;
42 private SettingsFragment mSettingsFragment;
43
44 @Override
45 protected void onCreate(Bundle savedInstanceState) {
46 super.onCreate(savedInstanceState);
47 FragmentManager fm = getFragmentManager();
48 mSettingsFragment = (SettingsFragment) fm.findFragmentById(android.R.id.content);
49 if (mSettingsFragment == null || !mSettingsFragment.getClass().equals(SettingsFragment.class)) {
50 mSettingsFragment = new SettingsFragment();
51 fm.beginTransaction().replace(android.R.id.content, mSettingsFragment).commit();
52 }
53
54 this.mTheme = findTheme();
55 setTheme(this.mTheme);
56
57 int bgcolor = getPrimaryBackgroundColor();
58 getWindow().getDecorView().setBackgroundColor(bgcolor);
59
60 }
61
62 @Override
63 void onBackendConnected() {
64
65 }
66
67 @Override
68 public void onStart() {
69 super.onStart();
70 PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
71 ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource");
72 if (resources != null) {
73 ArrayList<CharSequence> entries = new ArrayList<>(Arrays.asList(resources.getEntries()));
74 if (!entries.contains(Build.MODEL)) {
75 entries.add(0, Build.MODEL);
76 resources.setEntries(entries.toArray(new CharSequence[entries.size()]));
77 resources.setEntryValues(entries.toArray(new CharSequence[entries.size()]));
78 }
79 }
80
81 if (Config.FORCE_ORBOT) {
82 PreferenceCategory connectionOptions = (PreferenceCategory) mSettingsFragment.findPreference("connection_options");
83 PreferenceScreen expert = (PreferenceScreen) mSettingsFragment.findPreference("expert");
84 if (connectionOptions != null) {
85 expert.removePreference(connectionOptions);
86 }
87 }
88
89 final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates");
90 removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
91 @Override
92 public boolean onPreferenceClick(Preference preference) {
93 final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager();
94 final ArrayList<String> aliases = Collections.list(mtm.getCertificates());
95 if (aliases.size() == 0) {
96 displayToast(getString(R.string.toast_no_trusted_certs));
97 return true;
98 }
99 final ArrayList selectedItems = new ArrayList<>();
100 final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this);
101 dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title));
102 dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null,
103 new DialogInterface.OnMultiChoiceClickListener() {
104 @Override
105 public void onClick(DialogInterface dialog, int indexSelected,
106 boolean isChecked) {
107 if (isChecked) {
108 selectedItems.add(indexSelected);
109 } else if (selectedItems.contains(indexSelected)) {
110 selectedItems.remove(Integer.valueOf(indexSelected));
111 }
112 if (selectedItems.size() > 0)
113 ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
114 else {
115 ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
116 }
117 }
118 });
119
120 dialogBuilder.setPositiveButton(
121 getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() {
122 @Override
123 public void onClick(DialogInterface dialog, int which) {
124 int count = selectedItems.size();
125 if (count > 0) {
126 for (int i = 0; i < count; i++) {
127 try {
128 Integer item = Integer.valueOf(selectedItems.get(i).toString());
129 String alias = aliases.get(item);
130 mtm.deleteCertificate(alias);
131 } catch (KeyStoreException e) {
132 e.printStackTrace();
133 displayToast("Error: " + e.getLocalizedMessage());
134 }
135 }
136 if (xmppConnectionServiceBound) {
137 reconnectAccounts();
138 }
139 displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
140 }
141 }
142 });
143 dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
144 AlertDialog removeCertsDialog = dialogBuilder.create();
145 removeCertsDialog.show();
146 removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
147 return true;
148 }
149 });
150
151 final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs");
152 exportLogsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
153 @Override
154 public boolean onPreferenceClick(Preference preference) {
155 if (hasStoragePermission(REQUEST_WRITE_LOGS)) {
156 startExport();
157 }
158 return true;
159 }
160 });
161
162 if (Config.ONLY_INTERNAL_STORAGE) {
163 final Preference cleanCachePreference = mSettingsFragment.findPreference("clean_cache");
164 cleanCachePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
165 @Override
166 public boolean onPreferenceClick(Preference preference) {
167 cleanCache();
168 return true;
169 }
170 });
171
172 final Preference cleanPrivateStoragePreference = mSettingsFragment.findPreference("clean_private_storage");
173 cleanPrivateStoragePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
174 @Override
175 public boolean onPreferenceClick(Preference preference) {
176 cleanPrivateStorage();
177 return true;
178 }
179 });
180 }
181
182 final Preference deleteOmemoPreference = mSettingsFragment.findPreference("delete_omemo_identities");
183 deleteOmemoPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
184 @Override
185 public boolean onPreferenceClick(Preference preference) {
186 deleteOmemoIdentities();
187 return true;
188 }
189 });
190 }
191
192 private void cleanCache() {
193 Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
194 intent.setData(Uri.parse("package:" + getPackageName()));
195 startActivity(intent);
196 }
197
198 private void cleanPrivateStorage() {
199 cleanPrivatePictures();
200 cleanPrivateFiles();
201 }
202
203 private void cleanPrivatePictures() {
204 try {
205 File dir = new File(getFilesDir().getAbsolutePath(), "/Pictures/");
206 File[] array = dir.listFiles();
207 if (array != null) {
208 for (int b = 0; b < array.length; b++) {
209 String name = array[b].getName().toLowerCase();
210 if (name.equals(".nomedia")) {
211 continue;
212 }
213 if (array[b].isFile()) {
214 array[b].delete();
215 }
216 }
217 }
218 } catch (Throwable e) {
219 Log.e("CleanCache", e.toString());
220 }
221 }
222
223 private void cleanPrivateFiles() {
224 try {
225 File dir = new File(getFilesDir().getAbsolutePath(), "/Files/");
226 File[] array = dir.listFiles();
227 if (array != null) {
228 for (int b = 0; b < array.length; b++) {
229 String name = array[b].getName().toLowerCase();
230 if (name.equals(".nomedia")) {
231 continue;
232 }
233 if (array[b].isFile()) {
234 array[b].delete();
235 }
236 }
237 }
238 } catch (Throwable e) {
239 Log.e("CleanCache", e.toString());
240 }
241 }
242
243 private void deleteOmemoIdentities() {
244 AlertDialog.Builder builder = new AlertDialog.Builder(this);
245 builder.setTitle(R.string.pref_delete_omemo_identities);
246 final List<CharSequence> accounts = new ArrayList<>();
247 for(Account account : xmppConnectionService.getAccounts()) {
248 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
249 accounts.add(account.getJid().toBareJid().toString());
250 }
251 }
252 final boolean[] checkedItems = new boolean[accounts.size()];
253 builder.setMultiChoiceItems(accounts.toArray(new CharSequence[accounts.size()]), checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
254 @Override
255 public void onClick(DialogInterface dialog, int which, boolean isChecked) {
256 checkedItems[which] = isChecked;
257 final AlertDialog alertDialog = (AlertDialog) dialog;
258 for(boolean item : checkedItems) {
259 if (item) {
260 alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
261 return;
262 }
263 }
264 alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
265 }
266 });
267 builder.setNegativeButton(R.string.cancel,null);
268 builder.setPositiveButton(R.string.delete_selected_keys, new DialogInterface.OnClickListener() {
269 @Override
270 public void onClick(DialogInterface dialog, int which) {
271 for(int i = 0; i < checkedItems.length; ++i) {
272 if (checkedItems[i]) {
273 try {
274 Jid jid = Jid.fromString(accounts.get(i).toString());
275 Account account = xmppConnectionService.findAccountByJid(jid);
276 if (account != null) {
277 account.getAxolotlService().regenerateKeys(true);
278 }
279 } catch (InvalidJidException e) {
280 //
281 }
282
283 }
284 }
285 }
286 });
287 AlertDialog dialog = builder.create();
288 dialog.show();
289 dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
290 }
291
292 @Override
293 public void onStop() {
294 super.onStop();
295 PreferenceManager.getDefaultSharedPreferences(this)
296 .unregisterOnSharedPreferenceChangeListener(this);
297 }
298
299 @Override
300 public void onSharedPreferenceChanged(SharedPreferences preferences, String name) {
301 final List<String> resendPresence = Arrays.asList(
302 "confirm_messages",
303 "xa_on_silent_mode",
304 "away_when_screen_off",
305 "allow_message_correction",
306 "treat_vibrate_as_silent",
307 "manually_change_presence",
308 "last_activity");
309 if (name.equals("resource")) {
310 String resource = preferences.getString("resource", "mobile")
311 .toLowerCase(Locale.US);
312 if (xmppConnectionServiceBound) {
313 for (Account account : xmppConnectionService.getAccounts()) {
314 if (account.setResource(resource)) {
315 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
316 XmppConnection connection = account.getXmppConnection();
317 if (connection != null) {
318 connection.resetStreamId();
319 }
320 xmppConnectionService.reconnectAccountInBackground(account);
321 }
322 }
323 }
324 }
325 } else if (name.equals("keep_foreground_service")) {
326 xmppConnectionService.toggleForegroundService();
327 } else if (resendPresence.contains(name)) {
328 if (xmppConnectionServiceBound) {
329 if (name.equals("away_when_screen_off")
330 || name.equals("manually_change_presence")) {
331 xmppConnectionService.toggleScreenEventReceiver();
332 }
333 if (name.equals("manually_change_presence") && !noAccountUsesPgp()) {
334 Toast.makeText(this, R.string.republish_pgp_keys, Toast.LENGTH_LONG).show();
335 }
336 xmppConnectionService.refreshAllPresences();
337 }
338 } else if (name.equals("dont_trust_system_cas")) {
339 xmppConnectionService.updateMemorizingTrustmanager();
340 reconnectAccounts();
341 } else if (name.equals("use_tor")) {
342 reconnectAccounts();
343 }
344
345 }
346
347 @Override
348 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
349 if (grantResults.length > 0)
350 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
351 if (requestCode == REQUEST_WRITE_LOGS) {
352 startExport();
353 }
354 } else {
355 Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
356 }
357 }
358
359 private void startExport() {
360 startService(new Intent(getApplicationContext(), ExportLogsService.class));
361 }
362
363 private void displayToast(final String msg) {
364 runOnUiThread(new Runnable() {
365 @Override
366 public void run() {
367 Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show();
368 }
369 });
370 }
371
372 private void reconnectAccounts() {
373 for (Account account : xmppConnectionService.getAccounts()) {
374 if (!account.isOptionSet(Account.OPTION_DISABLED)) {
375 xmppConnectionService.reconnectAccountInBackground(account);
376 }
377 }
378 }
379
380 public void refreshUiReal() {
381 //nothing to do. This Activity doesn't implement any listeners
382 }
383
384}