SettingsActivity.java

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