diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt index 29ed6d101c..8a050b3ac6 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt @@ -84,7 +84,7 @@ class DebugMenuActivity : VectorBaseActivity() { @OnClick(R.id.debug_open_pin_code) fun openPinCode() { - startActivity(PinActivity.newIntent(this)) + //startActivity(PinActivity.newIntent(this)) } @OnClick(R.id.debug_test_notification) diff --git a/vector/src/main/java/im/vector/riotx/VectorApplication.kt b/vector/src/main/java/im/vector/riotx/VectorApplication.kt index 241240d7e3..361800623c 100644 --- a/vector/src/main/java/im/vector/riotx/VectorApplication.kt +++ b/vector/src/main/java/im/vector/riotx/VectorApplication.kt @@ -35,6 +35,7 @@ import com.gabrielittner.threetenbp.LazyThreeTen import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.MatrixConfiguration import im.vector.matrix.android.api.auth.AuthenticationService +import im.vector.matrix.android.api.extensions.orFalse import im.vector.matrix.android.api.legacy.LegacySessionImporter import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.DaggerVectorComponent @@ -47,6 +48,7 @@ import im.vector.riotx.features.disclaimer.doNotShowDisclaimerDialog import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks import im.vector.riotx.features.notifications.NotificationDrawerManager import im.vector.riotx.features.notifications.NotificationUtils +import im.vector.riotx.features.pin.PinCodeStore import im.vector.riotx.features.popup.PopupAlertManager import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler import im.vector.riotx.features.settings.VectorPreferences @@ -82,6 +84,7 @@ class VectorApplication : @Inject lateinit var appStateHandler: AppStateHandler @Inject lateinit var rxConfig: RxConfig @Inject lateinit var popupAlertManager: PopupAlertManager + @Inject lateinit var pinCodeStore: PinCodeStore lateinit var vectorComponent: VectorComponent diff --git a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt index 59bf7a8aeb..355211643c 100644 --- a/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt +++ b/vector/src/main/java/im/vector/riotx/core/platform/VectorBaseActivity.kt @@ -294,7 +294,6 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector { if (this !is BugReportActivity && vectorPreferences.useRageshake()) { rageShake.start() } - DebugReceiver .getIntentFilter(this) .takeIf { BuildConfig.DEBUG } diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt index 7d17545309..cda7695a12 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/DefaultNavigator.kt @@ -53,6 +53,8 @@ import im.vector.riotx.features.media.AttachmentData import im.vector.riotx.features.media.BigImageViewerActivity import im.vector.riotx.features.media.VectorAttachmentViewerActivity import im.vector.riotx.features.pin.PinActivity +import im.vector.riotx.features.pin.PinArgs +import im.vector.riotx.features.pin.PinMode import im.vector.riotx.features.roomdirectory.RoomDirectoryActivity import im.vector.riotx.features.roomdirectory.createroom.CreateRoomActivity import im.vector.riotx.features.roomdirectory.roompreview.RoomPreviewActivity @@ -251,7 +253,6 @@ class DefaultNavigator @Inject constructor( } } - override fun openTerms(fragment: Fragment, serviceType: TermsService.ServiceType, baseUrl: String, token: String?, requestCode: Int) { val intent = ReviewTermsActivity.intent(fragment.requireContext(), serviceType, baseUrl, token) fragment.startActivityForResult(intent, requestCode) @@ -274,9 +275,9 @@ class DefaultNavigator @Inject constructor( context.startActivity(WidgetActivity.newIntent(context, widgetArgs)) } - override fun openPinCode(activity: Activity, requestCode: Int) { - val intent = PinActivity.newIntent(activity) - activity.startActivity(intent) + override fun openPinCode(fragment: Fragment, pinMode: PinMode, requestCode: Int) { + val intent = PinActivity.newIntent(fragment.requireContext(), PinArgs(pinMode)) + fragment.startActivityForResult(intent, requestCode) } override fun openMediaViewer(activity: Activity, diff --git a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt index 7ffef9aa34..e5dea85040 100644 --- a/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/riotx/features/navigation/Navigator.kt @@ -28,6 +28,8 @@ import im.vector.matrix.android.api.session.widgets.model.Widget import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.features.home.room.detail.widget.WidgetRequestCodes import im.vector.riotx.features.media.AttachmentData +import im.vector.riotx.features.pin.PinActivity +import im.vector.riotx.features.pin.PinMode import im.vector.riotx.features.settings.VectorSettingsActivity import im.vector.riotx.features.share.SharedData import im.vector.riotx.features.terms.ReviewTermsActivity @@ -78,7 +80,7 @@ interface Navigator { fun openBigImageViewer(activity: Activity, sharedElement: View?, matrixItem: MatrixItem) - fun openPinCode(activity: Activity, requestCode: Int = ReviewTermsActivity.TERMS_REQUEST_CODE) + fun openPinCode(fragment: Fragment, pinMode: PinMode, requestCode: Int = PinActivity.PIN_REQUEST_CODE) fun openTerms(fragment: Fragment, serviceType: TermsService.ServiceType, diff --git a/vector/src/main/java/im/vector/riotx/features/pin/PinActivity.kt b/vector/src/main/java/im/vector/riotx/features/pin/PinActivity.kt index 27322c1539..8c2d9cb130 100644 --- a/vector/src/main/java/im/vector/riotx/features/pin/PinActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/pin/PinActivity.kt @@ -19,6 +19,7 @@ package im.vector.riotx.features.pin import android.content.Context import android.content.Intent import androidx.appcompat.widget.Toolbar +import com.airbnb.mvrx.MvRx import im.vector.riotx.R import im.vector.riotx.core.extensions.addFragment import im.vector.riotx.core.platform.ToolbarConfigurable @@ -28,8 +29,13 @@ class PinActivity : VectorBaseActivity(), ToolbarConfigurable { companion object { - fun newIntent(context: Context): Intent { - return Intent(context, PinActivity::class.java) + const val PIN_REQUEST_CODE = 17890 + const val PIN_RESULT_CODE_FORGOT = 17891 + + fun newIntent(context: Context, args: PinArgs): Intent { + return Intent(context, PinActivity::class.java).apply { + putExtra(MvRx.KEY_ARG, args) + } } } @@ -37,7 +43,8 @@ class PinActivity : VectorBaseActivity(), ToolbarConfigurable { override fun initUiAndData() { if (isFirstCreation()) { - addFragment(R.id.simpleFragmentContainer, PinFragment::class.java) + val fragmentArgs: PinArgs = intent?.extras?.getParcelable(MvRx.KEY_ARG) ?: return + addFragment(R.id.simpleFragmentContainer, PinFragment::class.java, fragmentArgs) } } diff --git a/vector/src/main/java/im/vector/riotx/features/pin/PinCodeStore.kt b/vector/src/main/java/im/vector/riotx/features/pin/PinCodeStore.kt index 3338bff610..33e4f22b81 100644 --- a/vector/src/main/java/im/vector/riotx/features/pin/PinCodeStore.kt +++ b/vector/src/main/java/im/vector/riotx/features/pin/PinCodeStore.kt @@ -18,35 +18,64 @@ package im.vector.riotx.features.pin import android.content.SharedPreferences import androidx.core.content.edit +import com.beautycoder.pflockscreen.security.PFResult +import com.beautycoder.pflockscreen.security.PFSecurityManager +import com.beautycoder.pflockscreen.security.callbacks.PFPinCodeHelperCallback +import im.vector.matrix.android.api.extensions.orFalse +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import javax.inject.Inject +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine interface PinCodeStore { - fun storeEncodedPin(encodePin: String) + suspend fun storeEncodedPin(encodePin: String) - fun deleteEncodedPin() + suspend fun deleteEncodedPin() fun getEncodedPin(): String? + + suspend fun hasEncodedPin(): Boolean } class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: SharedPreferences) : PinCodeStore { - override fun storeEncodedPin(encodePin: String) { + override suspend fun storeEncodedPin(encodePin: String) = withContext(Dispatchers.IO) { sharedPreferences.edit { putString(ENCODED_PIN_CODE_KEY, encodePin) } } - override fun deleteEncodedPin() { + override suspend fun deleteEncodedPin() = withContext(Dispatchers.IO) { sharedPreferences.edit { remove(ENCODED_PIN_CODE_KEY) } + awaitPinCodeCallback { + PFSecurityManager.getInstance().pinCodeHelper.delete(it) + } + return@withContext } override fun getEncodedPin(): String? { return sharedPreferences.getString(ENCODED_PIN_CODE_KEY, null) } + override suspend fun hasEncodedPin(): Boolean = withContext(Dispatchers.IO) { + val hasEncodedPin = getEncodedPin()?.isNotBlank().orFalse() + if (!hasEncodedPin) { + return@withContext false + } + val result = awaitPinCodeCallback { + PFSecurityManager.getInstance().pinCodeHelper.isPinCodeEncryptionKeyExist(it) + } + result.error == null && result.result + } + + private suspend inline fun awaitPinCodeCallback(crossinline callback: (PFPinCodeHelperCallback) -> Unit) = suspendCoroutine> { cont -> + callback(PFPinCodeHelperCallback { result -> cont.resume(result) }) + } + companion object { const val ENCODED_PIN_CODE_KEY = "ENCODED_PIN_CODE_KEY" } diff --git a/vector/src/main/java/im/vector/riotx/features/pin/PinFragment.kt b/vector/src/main/java/im/vector/riotx/features/pin/PinFragment.kt index e69d0ff1bc..4765d2f799 100644 --- a/vector/src/main/java/im/vector/riotx/features/pin/PinFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/pin/PinFragment.kt @@ -16,10 +16,14 @@ package im.vector.riotx.features.pin +import android.app.Activity import android.os.Bundle +import android.os.Parcelable import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.lifecycleScope +import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.beautycoder.pflockscreen.PFFLockScreenConfiguration @@ -27,60 +31,107 @@ import com.beautycoder.pflockscreen.fragments.PFLockScreenFragment import im.vector.riotx.R import im.vector.riotx.core.extensions.replaceFragment import im.vector.riotx.core.platform.VectorBaseFragment +import kotlinx.android.parcel.Parcelize +import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +@Parcelize +data class PinArgs( + val pinMode: PinMode +) : Parcelable + class PinFragment @Inject constructor( private val pinCodeStore: PinCodeStore, private val viewModelFactory: PinViewModel.Factory ) : VectorBaseFragment(), PinViewModel.Factory by viewModelFactory { private val viewModel: PinViewModel by fragmentViewModel() + private val fragmentArgs: PinArgs by args() override fun getLayoutResId() = R.layout.fragment_pin override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val encodedPinCode = pinCodeStore.getEncodedPin() - if (encodedPinCode.isNullOrBlank()) { - showCreateFragment() - } else { - showAuthFragment(encodedPinCode) + when (fragmentArgs.pinMode) { + PinMode.CREATE -> showCreateFragment() + PinMode.DELETE -> showDeleteFragment() + PinMode.AUTH -> showAuthFragment() } } + private fun showDeleteFragment() { + val encodedPin = pinCodeStore.getEncodedPin() ?: return + val authFragment = PFLockScreenFragment() + val builder = PFFLockScreenConfiguration.Builder(requireContext()) + .setUseFingerprint(true) + .setTitle(getString(R.string.auth_pin_confirm_to_disable_title)) + .setClearCodeOnError(true) + .setMode(PFFLockScreenConfiguration.MODE_AUTH) + authFragment.setConfiguration(builder.build()) + authFragment.setEncodedPinCode(encodedPin) + authFragment.setLoginListener(object : PFLockScreenFragment.OnPFLockScreenLoginListener { + override fun onPinLoginFailed() { + } + + override fun onFingerprintSuccessful() { + lifecycleScope.launch { + pinCodeStore.deleteEncodedPin() + vectorBaseActivity.setResult(Activity.RESULT_OK) + vectorBaseActivity.finish() + } + } + + override fun onFingerprintLoginFailed() { + } + + override fun onCodeInputSuccessful() { + lifecycleScope.launch { + pinCodeStore.deleteEncodedPin() + vectorBaseActivity.setResult(Activity.RESULT_OK) + vectorBaseActivity.finish() + } + } + }) + replaceFragment(R.id.pinFragmentContainer, authFragment) + } + private fun showCreateFragment() { val createFragment = PFLockScreenFragment() val builder = PFFLockScreenConfiguration.Builder(requireContext()) .setNewCodeValidation(true) - .setTitle("Choose a PIN for security") - .setNewCodeValidationTitle("Confirm PIN") + .setTitle(getString(R.string.create_pin_title)) + .setNewCodeValidationTitle(getString(R.string.create_pin_confirm_title)) .setMode(PFFLockScreenConfiguration.MODE_CREATE) createFragment.setConfiguration(builder.build()) createFragment.setCodeCreateListener(object : PFLockScreenFragment.OnPFLockScreenCodeCreateListener { override fun onNewCodeValidationFailed() { - Toast.makeText(requireContext(), "Failed to validate pin, please tap a new one.", Toast.LENGTH_SHORT).show() + Toast.makeText(requireContext(), getString(R.string.create_pin_confirm_failure), Toast.LENGTH_SHORT).show() } override fun onCodeCreated(encodedCode: String) { - pinCodeStore.storeEncodedPin(encodedCode) - vectorBaseActivity.finish() + lifecycleScope.launch { + pinCodeStore.storeEncodedPin(encodedCode) + vectorBaseActivity.setResult(Activity.RESULT_OK) + vectorBaseActivity.finish() + } } }) replaceFragment(R.id.pinFragmentContainer, createFragment) } - private fun showAuthFragment(encodedCode: String) { + private fun showAuthFragment() { + val encodedPin = pinCodeStore.getEncodedPin() ?: return val authFragment = PFLockScreenFragment() val builder = PFFLockScreenConfiguration.Builder(requireContext()) .setUseFingerprint(true) - .setTitle("Enter your PIN") - .setLeftButton("Forgot PIN?") + .setTitle(getString(R.string.auth_pin_title)) + .setLeftButton(getString(R.string.auth_pin_forgot)) .setClearCodeOnError(true) .setMode(PFFLockScreenConfiguration.MODE_AUTH) authFragment.setConfiguration(builder.build()) - authFragment.setEncodedPinCode(encodedCode) + authFragment.setEncodedPinCode(encodedPin) authFragment.setOnLeftButtonClickListener { displayForgotPinWarningDialog() } @@ -106,11 +157,14 @@ class PinFragment @Inject constructor( private fun displayForgotPinWarningDialog() { AlertDialog.Builder(requireContext()) - .setTitle("Reset pin") - .setMessage("To reset your PIN, you'll need to re-login and create a new one.") - .setPositiveButton("Reset pin") { _, _ -> - pinCodeStore.deleteEncodedPin() - vectorBaseActivity.finish() + .setTitle(getString(R.string.auth_pin_reset_title)) + .setMessage(getString(R.string.auth_pin_reset_content)) + .setPositiveButton(getString(R.string.auth_pin_reset_title)) { _, _ -> + lifecycleScope.launch { + pinCodeStore.deleteEncodedPin() + vectorBaseActivity.setResult(PinActivity.PIN_RESULT_CODE_FORGOT) + vectorBaseActivity.finish() + } } .setNegativeButton(R.string.cancel, null) .show() diff --git a/vector/src/main/java/im/vector/riotx/features/pin/PinMode.kt b/vector/src/main/java/im/vector/riotx/features/pin/PinMode.kt new file mode 100644 index 0000000000..952a0e3e94 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/pin/PinMode.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotx.features.pin + +enum class PinMode { + CREATE, + DELETE, + AUTH +} diff --git a/vector/src/main/java/im/vector/riotx/features/pin/PinViewState.kt b/vector/src/main/java/im/vector/riotx/features/pin/PinViewState.kt index 45eb9f72c2..34ba726d5c 100644 --- a/vector/src/main/java/im/vector/riotx/features/pin/PinViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/pin/PinViewState.kt @@ -18,4 +18,7 @@ package im.vector.riotx.features.pin import com.airbnb.mvrx.MvRxState -data class PinViewState(val flag: Boolean = false) : MvRxState +data class PinViewState(val mode: PinMode) : MvRxState { + + constructor(args: PinArgs) : this(mode = args.pinMode) +} diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index 0d12adb5f8..2d349696bf 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -816,6 +816,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { * The user enable protecting app access with pin code */ fun useFlagPinCode(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_FLAG_SECURE, false) + return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_PIN_CODE_FLAG, false) } } diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 9d71c1712e..b1fca9841b 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -25,6 +25,7 @@ import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.SwitchPreference @@ -52,14 +53,21 @@ import im.vector.riotx.features.crypto.keys.KeysExporter import im.vector.riotx.features.crypto.keys.KeysImporter import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet +import im.vector.riotx.features.navigation.Navigator +import im.vector.riotx.features.pin.PinActivity +import im.vector.riotx.features.pin.PinCodeStore +import im.vector.riotx.features.pin.PinMode import im.vector.riotx.features.themes.ThemeUtils import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable +import kotlinx.coroutines.launch import javax.inject.Inject class VectorSettingsSecurityPrivacyFragment @Inject constructor( private val vectorPreferences: VectorPreferences, - private val activeSessionHolder: ActiveSessionHolder + private val activeSessionHolder: ActiveSessionHolder, + private val pinCodeStore: PinCodeStore, + private val navigator: Navigator ) : VectorSettingsBaseFragment() { override var titleRes = R.string.settings_security_and_privacy @@ -96,6 +104,10 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( findPreference(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY)!! } + private val usePinCodePref by lazy { + findPreference(VectorPreferences.SETTINGS_SECURITY_USE_PIN_CODE_FLAG)!! + } + override fun onResume() { super.onResume() // My device name may have been updated @@ -214,6 +226,8 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( } } + refreshPinCodeStatus() + refreshXSigningStatus() secureBackupPreference.icon = activity?.let { @@ -283,10 +297,27 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( }) } } + } else if (requestCode == PinActivity.PIN_REQUEST_CODE) { + refreshPinCodeStatus() + } else if (requestCode == REQUEST_E2E_FILE_REQUEST_CODE) { + if (resultCode == Activity.RESULT_OK) { + importKeys(data) + } } - if (resultCode == Activity.RESULT_OK) { - when (requestCode) { - REQUEST_E2E_FILE_REQUEST_CODE -> importKeys(data) + } + + private fun refreshPinCodeStatus() { + lifecycleScope.launchWhenResumed { + val hasPinCode = pinCodeStore.hasEncodedPin() + usePinCodePref.isChecked = hasPinCode + usePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + val pinMode = if (hasPinCode) { + PinMode.DELETE + } else { + PinMode.CREATE + } + navigator.openPinCode(this@VectorSettingsSecurityPrivacyFragment, pinMode) + true } } } diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 1eb4b312cb..6a62c33d6b 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -2572,4 +2572,14 @@ Not all features in Riot are implemented in Element yet. Main missing (and comin Push notifications are disabled Review your settings to enable push notifications + Choose a PIN for security + Confirm PIN + Failed to validate pin, please tap a new one. + Enter your PIN + Forgot PIN? + Reset pin + To reset your PIN, you\'ll need to re-login and create a new one. + Enable PIN + If you want to reset your PIN, tap Forgot PIN to logout and reset. + Confirm PIN to disable PIN diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index 9bfe5e944b..7b621d254c 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -108,6 +108,12 @@ android:summary="@string/settings_security_prevent_screenshots_summary" android:title="@string/settings_security_prevent_screenshots_title" /> + + \ No newline at end of file