При разработке приложения, которое имеет доступ к важным данным пользователя (например, финансовые данные), не плохо бы добавить дополнительный слой безопасности. А именно - визуально защитить контент, пока пользователь не подтвердил свой отпечаток и скрыть контент приложения из меню “Недавние приложения” (Recent Apps).

Пользователь новых iOS сразу же может открыть любое плюс-минус адекватное приложение для мобильного банкинга и (скорее всего) он увидит, что проблема решается добавлением размытия контента (blur) в нужные моменты.

Именно такие входящие данные я и получил для разрешения данной задачи. Сразу скажу, что требование именно блюрить контент добавило работы и по итогу мы от него отказались. Но обо всем по порядку.

Защита контента в ожидании отпечатка пользователя

Юзкейс - пользователь запускает приложение или выводит его из фона. Нужно спросить отпечаток и показать контент только когда биометрика подтверждена.

Итак, как же скрыть контент?

Первое, что приходит в голову - размыть (заблюрить) его.

Начиная с Android 12 добавилось новое API RenderEffect. Android Compose тоже получил весьма удобный модификатор blur. Новость-то хорошая, но как насчет девайсов с меньшими API?

Как выглядит blur на Android 12
Как выглядит blur на Android 12

***

Для Android 8.0+ есть 3rd party решение blurkit. Все еще не идеально, во-первых, потому что это зависимость, а во-вторых, мое приложение работает и на низших версиях Андроида.

Помимо отсутствия нативных решений для младших версий Андроида, есть еще одна неочевидная особенность - если на UI, который под блюром, происходит какая-то операция или проигрывается анимация - её будет видно (хоть и плохо) и это отвлекает.

В итоге было принято решение просто показывать статичную view, что является очень прямолинейным и элегантным решением. Меньше кода - меньше багов.

Пример на Xiaomi Mi 10T Pro (Android 10)
Пример на Xiaomi Mi 10T Pro (Android 10)

***

Код будет в конце, а пока давайте посмотрим на вторую проблему - блюрить приложение в недавних.

Защита контента приложения в Recent Apps

Начал я с уже проделанного сообществом труда, т.е. со Stackoverflow.

Итого, есть следующие варианты:

  1. Системное решение android:excludeFromRecents - скрывает приложение из недавних. Отбрасываем, т.е. ухудшает UX. Попробуй объясни потом пользователям, куда девается апка.

  2. Сторонние решения, например myopic-app-switcher, который под капотом использует тот же blurkit-android. Как вариант, но работает с Android 8.0 и, опять-таки, это зависимость, которой надо поверить, что она действительно всё заблюрит, а не упадет на каком-то баге.

  3. Системное решение FLAG_SECURE - весьма интересная фича. Суть в том, что в недавних юзер видит белый экран + не может сделать скрин апки стандартными средствами. Проблему невозможности сделать скрин можно обойти, устанавливая флаг в onPause и сбрасывая в onResume, таким образом вычленим из функционала только сокрытие контента из Recent Apps. У меня не завелось на Android 6.0 и 7.0, а соотв. работает тоже с Android 8.0

Android 8.0 (API 26)
Android 8.0 (API 26)

***

  1. Показывать кастомную вьюху в onPause и скрывать ее в onResume. Отлично работает на Android 8.0+. На андроидах ниже система как-то по-другому делает скрин контента, который показывается в недавних, и поэтому способ не работает. Очень простое решение без зависимостей.

Android 6.0 (API 23)
Android 6.0 (API 23)
Android 7.0 (API 24)
Android 7.0 (API 24)
Android 8.0 (API 26)
Android 8.0 (API 26)
Android 11.0 (API 30)
Android 11.0 (API 30)

***

Итого, решения ниже 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

Xiaomi Mi 10T Pro (Android 10) - устанавливаемые приложение не заблюрены по умолчанию. Фокус с RecentView не работает.
Xiaomi Mi 10T Pro (Android 10) - устанавливаемые приложение не заблюрены по умолчанию. Фокус с RecentView не работает.

***

Чтобы включить размытие, пользователю надо сходить в настройки и сделать это руками. По умолчанию размытие выключено на 99% приложений, которые я устанавливал. Не понятно, какой логике следует MIUI. Если есть предположения - пишите.

Xiaomi Mi 10T Pro (Android 10) - После того, как размытие было включено
Xiaomi Mi 10T Pro (Android 10) - После того, как размытие было включено

***

Возможно, это не единственный edge case, ведь прошивок великое множество.

Итого

Внешне функция сокрытия контента кажется чем-то незамысловатым, но нужно понимать, что это безопасность пользователя, поэтому время ей уделить стоит.

Резюмируя, идеальным решением будет установить minSdk = 26 (Android 8) и использовать любое из удобный решений, описанных выше.

Разработка под Android никогда не была легким занятием и подобные кейсы это доказывают. Фрагментарность системы - это то, с чем Андроид разработчикам приходится жить. Надеюсь, эта статья немного облегчит нам жизнь.

Успехов!

P.S. заходите в мой блог✌️

Комментарии (1)


  1. 13werwolf13
    03.02.2022 07:35
    +2

    Чтобы включить размытие, пользователю надо сходить в настройки и сделать это руками. По умолчанию размытие выключено на 99% приложений, которые я устанавливал. Не понятно, какой логике следует MIUI. Если есть предположения - пишите.

    при всей моей ненависти к miui тут я на их стороне. позвольте мне самому решать что и как делать на моём устройстве. хочу - включу размытие, хочу - выключу.
    хотя вопрос о "состоянии по умолчанию" пожалуй действительно стоит пересмотреть.