Skip to content
This repository was archived by the owner on Sep 23, 2024. It is now read-only.

Commit b9e7869

Browse files
feat(chat, notification and security): Add selective SignalJW Features
- Choose between passphrase protection and the Android screenlock - Option to treat view-once media as normal media - Option to ignore remote deletion - Choose between FCM or websocket notification delivery - Option to delete only the media from a message, not the rest of the message - Added options to select who can add you to a group Co-authored-by: Johan <[email protected]>
1 parent 067ec03 commit b9e7869

22 files changed

+540
-37
lines changed

app/src/main/AndroidManifest.xml

+3
Original file line numberDiff line numberDiff line change
@@ -663,9 +663,12 @@
663663
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
664664
android:exported="false"/>
665665

666+
667+
<!-- JW: added the windowSoftInputMode="stateAlwaysVisible" flag here to show a keyboard on open -->
666668
<activity android:name=".PassphrasePromptActivity"
667669
android:launchMode="singleTask"
668670
android:theme="@style/TextSecure.LightIntroTheme"
671+
android:windowSoftInputMode="stateAlwaysVisible"
669672
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
670673
android:exported="false"/>
671674

app/src/main/java/org/thoughtcrime/securesms/AppInitialization.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static void onFirstEverAppLaunch(@NonNull Context context) {
3333
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
3434
TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode());
3535
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
36-
TextSecurePreferences.setPasswordDisabled(context, true);
36+
//TextSecurePreferences.setPasswordDisabled(context, true); // JW: don't do this
3737
TextSecurePreferences.setReadReceiptsEnabled(context, true);
3838
TextSecurePreferences.setTypingIndicatorsEnabled(context, true);
3939
TextSecurePreferences.setHasSeenWelcomeScreen(context, false);
@@ -73,7 +73,7 @@ public static void onRepairFirstEverAppLaunch(@NonNull Context context) {
7373
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
7474
TextSecurePreferences.setLastVersionCode(context, Util.getCanonicalVersionCode());
7575
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
76-
TextSecurePreferences.setPasswordDisabled(context, true);
76+
//TextSecurePreferences.setPasswordDisabled(context, true); // JW: don't do this
7777
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
7878
SignalStore.onFirstEverAppLaunch();
7979
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));

app/src/main/java/org/thoughtcrime/securesms/PassphrasePromptActivity.java

+5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import android.view.MenuItem;
3535
import android.view.View;
3636
import android.view.View.OnClickListener;
37+
import android.view.WindowManager; // JW: added
3738
import android.view.animation.Animation;
3839
import android.view.animation.BounceInterpolator;
3940
import android.view.animation.TranslateAnimation;
@@ -277,11 +278,15 @@ private void initializeResources() {
277278
private void setLockTypeVisibility() {
278279
if (TextSecurePreferences.isScreenLockEnabled(this)) {
279280
passphraseAuthContainer.setVisibility(View.GONE);
281+
// JW: to override setSoftInputMode it has to be defined in the manifest file
282+
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); // JW
280283
fingerprintPrompt.setVisibility(biometricManager.canAuthenticate(BiometricDeviceAuthentication.BIOMETRIC_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS ? View.VISIBLE
281284
: View.GONE);
282285
lockScreenButton.setVisibility(View.VISIBLE);
283286
} else {
284287
passphraseAuthContainer.setVisibility(View.VISIBLE);
288+
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); // JW
289+
passphraseText.requestFocus(); // JW
285290
fingerprintPrompt.setVisibility(View.GONE);
286291
lockScreenButton.setVisibility(View.GONE);
287292
}

app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsFragment.kt

+52
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.thoughtcrime.securesms.components.settings.app.chats
22

33
import android.content.Intent
4+
import android.os.Build // JW: added
45
import androidx.lifecycle.ViewModelProvider
56
import androidx.navigation.Navigation
67
import org.thoughtcrime.securesms.R
@@ -10,12 +11,15 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
1011
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
1112
import org.thoughtcrime.securesms.components.settings.configure
1213
import org.thoughtcrime.securesms.util.FeatureFlags
14+
import org.thoughtcrime.securesms.util.TextSecurePreferences // JW: added
1315
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
1416
import org.thoughtcrime.securesms.util.navigation.safeNavigate
1517

1618
class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__chats) {
1719

1820
private lateinit var viewModel: ChatsSettingsViewModel
21+
private val groupAddLabels by lazy { resources.getStringArray(R.array.pref_group_add_entries) } // JW: added
22+
private val groupAddValues by lazy { resources.getStringArray(R.array.pref_group_add_values) } // JW: added
1923

2024
override fun onResume() {
2125
super.onResume()
@@ -105,6 +109,54 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
105109
Navigation.findNavController(requireView()).safeNavigate(R.id.action_chatsSettingsFragment_to_backupsPreferenceFragment)
106110
}
107111
)
112+
113+
dividerPref()
114+
115+
sectionHeaderPref(R.string.preferences_chats__control_message_deletion)
116+
117+
// JW: added
118+
switchPref(
119+
title = DSLSettingsText.from(R.string.preferences_chats__chat_keep_view_once_messages),
120+
summary = DSLSettingsText.from(R.string.preferences_chats__keep_view_once_messages_summary),
121+
isChecked = state.keepViewOnceMessages,
122+
onClick = {
123+
viewModel.keepViewOnceMessages(!state.keepViewOnceMessages)
124+
}
125+
)
126+
127+
// JW: added
128+
switchPref(
129+
title = DSLSettingsText.from(R.string.preferences_chats__chat_ignore_remote_delete),
130+
summary = DSLSettingsText.from(R.string.preferences_chats__chat_ignore_remote_delete_summary),
131+
isChecked = state.ignoreRemoteDelete,
132+
onClick = {
133+
viewModel.ignoreRemoteDelete(!state.ignoreRemoteDelete)
134+
}
135+
)
136+
137+
// JW: added
138+
switchPref(
139+
title = DSLSettingsText.from(R.string.preferences_chats__delete_media_only),
140+
summary = DSLSettingsText.from(R.string.preferences_chats__delete_media_only_summary),
141+
isChecked = state.deleteMediaOnly,
142+
onClick = {
143+
viewModel.deleteMediaOnly(!state.deleteMediaOnly)
144+
}
145+
)
146+
147+
dividerPref()
148+
149+
sectionHeaderPref(R.string.preferences_chats__group_control)
150+
151+
// JW: added
152+
radioListPref(
153+
title = DSLSettingsText.from(R.string.preferences_chats__who_can_add_you_to_groups),
154+
listItems = groupAddLabels,
155+
selected = groupAddValues.indexOf(state.whoCanAddYouToGroups),
156+
onSelected = {
157+
viewModel.setWhoCanAddYouToGroups(groupAddValues[it])
158+
}
159+
)
108160
}
109161
}
110162
}

app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsState.kt

+6
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@ data class ChatsSettingsState(
88
val enterKeySends: Boolean,
99
val localBackupsEnabled: Boolean,
1010
val remoteBackupsEnabled: Boolean
11+
// JW: added extra preferences
12+
,
13+
val keepViewOnceMessages: Boolean,
14+
val ignoreRemoteDelete: Boolean,
15+
val deleteMediaOnly: Boolean,
16+
val whoCanAddYouToGroups: String,
1117
)

app/src/main/java/org/thoughtcrime/securesms/components/settings/app/chats/ChatsSettingsViewModel.kt

+50
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
66
import org.thoughtcrime.securesms.keyvalue.SignalStore
77
import org.thoughtcrime.securesms.util.BackupUtil
88
import org.thoughtcrime.securesms.util.ConversationUtil
9+
import org.thoughtcrime.securesms.util.TextSecurePreferences // JW: added
910
import org.thoughtcrime.securesms.util.ThrottledDebouncer
1011
import org.thoughtcrime.securesms.util.livedata.Store
1112

@@ -24,6 +25,12 @@ class ChatsSettingsViewModel @JvmOverloads constructor(
2425
enterKeySends = SignalStore.settings().isEnterKeySends,
2526
localBackupsEnabled = SignalStore.settings().isBackupEnabled && BackupUtil.canUserAccessBackupDirectory(ApplicationDependencies.getApplication()),
2627
remoteBackupsEnabled = SignalStore.backup().areBackupsEnabled
28+
// JW: added
29+
,
30+
keepViewOnceMessages = TextSecurePreferences.isKeepViewOnceMessages(ApplicationDependencies.getApplication()),
31+
ignoreRemoteDelete = TextSecurePreferences.isIgnoreRemoteDelete(ApplicationDependencies.getApplication()),
32+
deleteMediaOnly = TextSecurePreferences.isDeleteMediaOnly(ApplicationDependencies.getApplication()),
33+
whoCanAddYouToGroups = TextSecurePreferences.whoCanAddYouToGroups(ApplicationDependencies.getApplication()),
2734
)
2835
)
2936

@@ -63,5 +70,48 @@ class ChatsSettingsViewModel @JvmOverloads constructor(
6370
if (store.state.localBackupsEnabled != backupsEnabled) {
6471
store.update { it.copy(localBackupsEnabled = backupsEnabled) }
6572
}
73+
// JW: added. This is required to update the UI for settings that are not in the
74+
// Signal store but in the shared preferences.
75+
store.update { getState().copy() }
6676
}
77+
78+
// JW: added
79+
fun keepViewOnceMessages(enabled: Boolean) {
80+
TextSecurePreferences.setKeepViewOnceMessages(ApplicationDependencies.getApplication(), enabled)
81+
refresh()
82+
}
83+
84+
// JW: added
85+
fun ignoreRemoteDelete(enabled: Boolean) {
86+
TextSecurePreferences.setIgnoreRemoteDelete(ApplicationDependencies.getApplication(), enabled)
87+
refresh()
88+
}
89+
90+
// JW: added
91+
fun deleteMediaOnly(enabled: Boolean) {
92+
TextSecurePreferences.setDeleteMediaOnly(ApplicationDependencies.getApplication(), enabled)
93+
refresh()
94+
}
95+
96+
97+
// JW: added
98+
fun setWhoCanAddYouToGroups(adder: String) {
99+
TextSecurePreferences.setWhoCanAddYouToGroups(ApplicationDependencies.getApplication(), adder)
100+
refresh()
101+
}
102+
103+
// JW: added
104+
private fun getState() = ChatsSettingsState(
105+
generateLinkPreviews = SignalStore.settings().isLinkPreviewsEnabled,
106+
useAddressBook = SignalStore.settings().isPreferSystemContactPhotos,
107+
keepMutedChatsArchived = SignalStore.settings().shouldKeepMutedChatsArchived(),
108+
useSystemEmoji = SignalStore.settings().isPreferSystemEmoji,
109+
enterKeySends = SignalStore.settings().isEnterKeySends,
110+
localBackupsEnabled = SignalStore.settings().isBackupEnabled,
111+
remoteBackupsEnabled = SignalStore.backup().areBackupsEnabled,
112+
keepViewOnceMessages = TextSecurePreferences.isKeepViewOnceMessages(ApplicationDependencies.getApplication()),
113+
ignoreRemoteDelete = TextSecurePreferences.isIgnoreRemoteDelete(ApplicationDependencies.getApplication()),
114+
deleteMediaOnly = TextSecurePreferences.isDeleteMediaOnly(ApplicationDependencies.getApplication()),
115+
whoCanAddYouToGroups = TextSecurePreferences.whoCanAddYouToGroups(ApplicationDependencies.getApplication())
116+
)
67117
}

app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsFragment.kt

+54-21
Original file line numberDiff line numberDiff line change
@@ -181,30 +181,62 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
181181

182182
sectionHeaderPref(R.string.PrivacySettingsFragment__app_security)
183183

184-
if (state.isObsoletePasswordEnabled) {
184+
// JW: added toggle between password and Android screenlock
185+
switchPref(
186+
title = DSLSettingsText.from(R.string.preferences_app_protection__method_passphrase),
187+
summary = DSLSettingsText.from(R.string.preferences_app_protection__method_passphrase_summary),
188+
isChecked = state.isProtectionMethodPassphrase,
189+
onClick = {
190+
// After a togggle, we disable both passphrase and Android keylock.
191+
// Remove the passphrase if there is one set
192+
if (state.isObsoletePasswordEnabled) {
193+
MasterSecretUtil.changeMasterSecretPassphrase(
194+
activity,
195+
KeyCachingService.getMasterSecret(context),
196+
MasterSecretUtil.UNENCRYPTED_PASSPHRASE
197+
)
198+
TextSecurePreferences.setPasswordDisabled(activity, true)
199+
val intent = Intent(context, KeyCachingService::class.java)
200+
intent.action = KeyCachingService.DISABLE_ACTION
201+
requireActivity().startService(intent)
202+
}
203+
TextSecurePreferences.setProtectionMethod(activity, !state.isProtectionMethodPassphrase)
204+
viewModel.setNoLock()
205+
}
206+
)
207+
208+
//if (state.isObsoletePasswordEnabled) {
209+
if (viewModel.isPassphraseSelected()) { // JW: method changed
185210
switchPref(
186211
title = DSLSettingsText.from(R.string.preferences__enable_passphrase),
187212
summary = DSLSettingsText.from(R.string.preferences__lock_signal_and_message_notifications_with_a_passphrase),
188-
isChecked = true,
213+
isChecked = state.isObsoletePasswordEnabled, // JW,
189214
onClick = {
190-
MaterialAlertDialogBuilder(requireContext()).apply {
191-
setTitle(R.string.ApplicationPreferencesActivity_disable_passphrase)
192-
setMessage(R.string.ApplicationPreferencesActivity_this_will_permanently_unlock_signal_and_message_notifications)
193-
setIcon(R.drawable.symbol_error_triangle_fill_24)
194-
setPositiveButton(R.string.ApplicationPreferencesActivity_disable) { _, _ ->
195-
MasterSecretUtil.changeMasterSecretPassphrase(
196-
activity,
197-
KeyCachingService.getMasterSecret(context),
198-
MasterSecretUtil.UNENCRYPTED_PASSPHRASE
199-
)
200-
TextSecurePreferences.setPasswordDisabled(activity, true)
201-
val intent = Intent(activity, KeyCachingService::class.java)
202-
intent.action = KeyCachingService.DISABLE_ACTION
203-
requireActivity().startService(intent)
204-
viewModel.refresh()
215+
if (state.isObsoletePasswordEnabled) { // JW: added if else
216+
MaterialAlertDialogBuilder(requireContext()).apply {
217+
setTitle(R.string.ApplicationPreferencesActivity_disable_passphrase)
218+
setMessage(R.string.ApplicationPreferencesActivity_this_will_permanently_unlock_signal_and_message_notifications)
219+
setIcon(R.drawable.symbol_error_triangle_fill_24)
220+
setPositiveButton(R.string.ApplicationPreferencesActivity_disable) { _, _ ->
221+
MasterSecretUtil.changeMasterSecretPassphrase(
222+
activity,
223+
KeyCachingService.getMasterSecret(context),
224+
MasterSecretUtil.UNENCRYPTED_PASSPHRASE
225+
)
226+
TextSecurePreferences.setPasswordDisabled(activity, true)
227+
val intent = Intent(activity, KeyCachingService::class.java)
228+
intent.action = KeyCachingService.DISABLE_ACTION
229+
requireActivity().startService(intent)
230+
viewModel.refresh()
231+
}
232+
setNegativeButton(android.R.string.cancel, null)
233+
show()
205234
}
206-
setNegativeButton(android.R.string.cancel, null)
207-
show()
235+
} else {
236+
// enable password
237+
val intent = Intent(activity, PassphraseChangeActivity::class.java)
238+
startActivity(intent)
239+
viewModel.refresh()
208240
}
209241
}
210242
)
@@ -236,14 +268,15 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
236268

237269
clickPref(
238270
title = DSLSettingsText.from(R.string.preferences__inactivity_timeout_interval),
271+
summary = DSLSettingsText.from(getScreenLockInactivityTimeoutSummary(60 * state.obsoletePasswordTimeout.toLong())), // JW
239272
onClick = {
240273
childFragmentManager.clearFragmentResult(TimeDurationPickerDialog.RESULT_DURATION)
241274
childFragmentManager.clearFragmentResultListener(TimeDurationPickerDialog.RESULT_DURATION)
242275
childFragmentManager.setFragmentResultListener(TimeDurationPickerDialog.RESULT_DURATION, this@PrivacySettingsFragment) { _, bundle ->
243276
val timeout = bundle.getLong(TimeDurationPickerDialog.RESULT_KEY_DURATION_MILLISECONDS).milliseconds.inWholeMinutes.toInt()
244277
viewModel.setObsoletePasswordTimeout(max(timeout, 1))
245278
}
246-
TimeDurationPickerDialog.create(state.screenLockActivityTimeout.seconds).show(childFragmentManager, null)
279+
TimeDurationPickerDialog.create(state.obsoletePasswordTimeout.seconds * 60).show(childFragmentManager, null) // JW
247280
}
248281
)
249282
} else {
@@ -255,7 +288,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
255288
isChecked = state.screenLock && isKeyguardSecure,
256289
isEnabled = isKeyguardSecure,
257290
onClick = {
258-
viewModel.setScreenLockEnabled(!state.screenLock)
291+
viewModel.setOnlyScreenlockEnabled(!state.screenLock) // JW: changed
259292

260293
val intent = Intent(requireContext(), KeyCachingService::class.java)
261294
intent.action = KeyCachingService.LOCK_TOGGLED_EVENT

app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsState.kt

+3
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ data class PrivacySettingsState(
1313
val isObsoletePasswordTimeoutEnabled: Boolean,
1414
val obsoletePasswordTimeout: Int,
1515
val universalExpireTimer: Int
16+
// JW: added
17+
,
18+
val isProtectionMethodPassphrase: Boolean
1619
)

app/src/main/java/org/thoughtcrime/securesms/components/settings/app/privacy/PrivacySettingsViewModel.kt

+38
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,41 @@ class PrivacySettingsViewModel(
7272
refresh()
7373
}
7474

75+
// JW: added
76+
fun setPassphraseEnabled(enabled: Boolean) {
77+
sharedPreferences.edit().putBoolean(TextSecurePreferences.DISABLE_PASSPHRASE_PREF, !enabled).apply()
78+
sharedPreferences.edit().putBoolean("pref_enable_passphrase_temporary", enabled).apply()
79+
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_LOCK, !enabled).apply()
80+
refresh()
81+
}
82+
83+
// JW: added
84+
fun setOnlyScreenlockEnabled(enabled: Boolean) {
85+
sharedPreferences.edit().putBoolean(TextSecurePreferences.DISABLE_PASSPHRASE_PREF, true).apply()
86+
sharedPreferences.edit().putBoolean("pref_enable_passphrase_temporary", false).apply()
87+
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_LOCK, enabled).apply()
88+
refresh()
89+
}
90+
91+
// JW: added
92+
fun setNoLock() {
93+
sharedPreferences.edit().putBoolean(TextSecurePreferences.DISABLE_PASSPHRASE_PREF, true).apply()
94+
sharedPreferences.edit().putBoolean("pref_enable_passphrase_temporary", false).apply()
95+
sharedPreferences.edit().putBoolean(TextSecurePreferences.SCREEN_LOCK, false).apply()
96+
refresh()
97+
}
98+
99+
// JW: added method.
100+
fun isPassphraseSelected(): Boolean {
101+
// Because this preference may be undefined when this app is first ran we also check if there is a passphrase
102+
// defined, if so, we assume passphrase protection:
103+
val myContext = ApplicationDependencies.getApplication()
104+
105+
return TextSecurePreferences.isProtectionMethodPassphrase(myContext) ||
106+
TextSecurePreferences.getBooleanPreference(myContext, "pref_enable_passphrase_temporary", false) &&
107+
!TextSecurePreferences.isPasswordDisabled(myContext)
108+
}
109+
75110
fun refresh() {
76111
store.update(this::updateState)
77112
}
@@ -90,6 +125,9 @@ class PrivacySettingsViewModel(
90125
isObsoletePasswordTimeoutEnabled = TextSecurePreferences.isPassphraseTimeoutEnabled(ApplicationDependencies.getApplication()),
91126
obsoletePasswordTimeout = TextSecurePreferences.getPassphraseTimeoutInterval(ApplicationDependencies.getApplication()),
92127
universalExpireTimer = SignalStore.settings().universalExpireTimer
128+
// JW: added
129+
,
130+
isProtectionMethodPassphrase = TextSecurePreferences.isProtectionMethodPassphrase(ApplicationDependencies.getApplication())
93131
)
94132
}
95133

0 commit comments

Comments
 (0)