При разработке приложения, которое имеет доступ к важным данным пользователя (например, финансовые данные), не плохо бы добавить дополнительный слой безопасности. А именно - визуально защитить контент, пока пользователь не подтвердил свой отпечаток и скрыть контент приложения из меню “Недавние приложения” (Recent Apps).
Пользователь новых iOS сразу же может открыть любое плюс-минус адекватное приложение для мобильного банкинга и (скорее всего) он увидит, что проблема решается добавлением размытия контента (blur) в нужные моменты.
Именно такие входящие данные я и получил для разрешения данной задачи. Сразу скажу, что требование именно блюрить контент добавило работы и по итогу мы от него отказались. Но обо всем по порядку.
Защита контента в ожидании отпечатка пользователя
Юзкейс - пользователь запускает приложение или выводит его из фона. Нужно спросить отпечаток и показать контент только когда биометрика подтверждена.
Итак, как же скрыть контент?
Первое, что приходит в голову - размыть (заблюрить) его.
Начиная с Android 12 добавилось новое API RenderEffect. Android Compose тоже получил весьма удобный модификатор blur. Новость-то хорошая, но как насчет девайсов с меньшими API?
***
Для Android 8.0+ есть 3rd party решение blurkit. Все еще не идеально, во-первых, потому что это зависимость, а во-вторых, мое приложение работает и на низших версиях Андроида.
Помимо отсутствия нативных решений для младших версий Андроида, есть еще одна неочевидная особенность - если на UI, который под блюром, происходит какая-то операция или проигрывается анимация - её будет видно (хоть и плохо) и это отвлекает.
В итоге было принято решение просто показывать статичную view, что является очень прямолинейным и элегантным решением. Меньше кода - меньше багов.
***
Код будет в конце, а пока давайте посмотрим на вторую проблему - блюрить приложение в недавних.
Защита контента приложения в Recent Apps
Начал я с уже проделанного сообществом труда, т.е. со Stackoverflow.
Итого, есть следующие варианты:
Системное решение android:excludeFromRecents - скрывает приложение из недавних. Отбрасываем, т.е. ухудшает UX. Попробуй объясни потом пользователям, куда девается апка.
Сторонние решения, например myopic-app-switcher, который под капотом использует тот же blurkit-android. Как вариант, но работает с Android 8.0 и, опять-таки, это зависимость, которой надо поверить, что она действительно всё заблюрит, а не упадет на каком-то баге.
Системное решение FLAG_SECURE - весьма интересная фича. Суть в том, что в недавних юзер видит белый экран + не может сделать скрин апки стандартными средствами. Проблему невозможности сделать скрин можно обойти, устанавливая флаг в onPause и сбрасывая в onResume, таким образом вычленим из функционала только сокрытие контента из Recent Apps. У меня не завелось на Android 6.0 и 7.0, а соотв. работает тоже с Android 8.0
***
Показывать кастомную вьюху в onPause и скрывать ее в onResume. Отлично работает на Android 8.0+. На андроидах ниже система как-то по-другому делает скрин контента, который показывается в недавних, и поэтому способ не работает. Очень простое решение без зависимостей.
***
Итого, решения ниже Android 8 по факту нет (кроме как совсем убрать из недавних). Мы приняли решение двигаться с вариантом №4.
Код
Пишу на Compose, если не понимаете - мои статьи о Compose.
MainActivity.kt
class MainActivity : FragmentActivity() {
private var recentAppsView: View? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
createRecentApppsView()
addRecentAppsView()
}
override fun onResume() {
super.onResume()
viewModel.checkSecurityAuth(
activity = this,
onSuccess = ::removeRecentAppsView,
onFailure = ::finish
)
}
override fun onPause() {
addRecentAppsView()
super.onPause()
}
private fun createRecentApppsView() {
recentAppsView = ComposeView(this).apply { setContent { RecentApps() } }
}
private fun addRecentAppsView() {
removeRecentAppsView()
recentAppsView?.let { (window.decorView as ViewGroup).addView(it) }
}
private fun removeRecentAppsView() =
recentAppsView?.let { (window.decorView as ViewGroup).removeView(it) }
}
RecentApps.kt
@Composable
fun RecentApps() =
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
StrictIcon(painter = painterResource(id = R.drawable.logo_light))
}
Edge case: Xiaomi MIUI
***
Чтобы включить размытие, пользователю надо сходить в настройки и сделать это руками. По умолчанию размытие выключено на 99% приложений, которые я устанавливал. Не понятно, какой логике следует MIUI. Если есть предположения - пишите.
***
Возможно, это не единственный edge case, ведь прошивок великое множество.
Итого
Внешне функция сокрытия контента кажется чем-то незамысловатым, но нужно понимать, что это безопасность пользователя, поэтому время ей уделить стоит.
Резюмируя, идеальным решением будет установить minSdk = 26 (Android 8) и использовать любое из удобный решений, описанных выше.
Разработка под Android никогда не была легким занятием и подобные кейсы это доказывают. Фрагментарность системы - это то, с чем Андроид разработчикам приходится жить. Надеюсь, эта статья немного облегчит нам жизнь.
Успехов!
P.S. заходите в мой блог✌️
13werwolf13
при всей моей ненависти к miui тут я на их стороне. позвольте мне самому решать что и как делать на моём устройстве. хочу - включу размытие, хочу - выключу.
хотя вопрос о "состоянии по умолчанию" пожалуй действительно стоит пересмотреть.