SettingsActivity.java

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