SettingsActivity.java

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