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