Android: Jak ustawić hasło / kod PIN tylko do mojej aplikacji?

Nov 19 2020

Pracuję nad aplikacją, w której muszę ustawić funkcję zabezpieczeń, ustawiając hasło / hasło / kod PIN dla użytkownika, aby za każdym razem, gdy otworzy aplikację w tle, aplikacja prosi o hasło / hasło / kod PIN. Czytałem kilka artykułów / rozwiązania, takie jak to , ale nie może mi pomóc. Ta sama funkcjonalność, którą znaleźliśmy w zwykłych aplikacjach bankowych, w których za każdym razem, gdy otwierasz aplikację, proszą o hasło / odcisk palca.

Skonfigurowałem już logikę, aby zapisać hasło / kod PIN we wspólnych preferencjach, ale nie jestem pewien, kiedy o to zapytać. Wiem, że nie możemy zastąpić ekranu powitalnego aktywnością kodu dostępu / kodu PIN, ponieważ czasami z poziomu ekranu głównego aplikacji / MainActivity użytkownik naciska przycisk home, a gdy ponownie otworzy aplikację z ostatnich aplikacji, aplikacja powinna poprosić o hasło / pin, aby wznowić użycie aplikacji.

Każda pomoc będzie mile widziana.

Odpowiedzi

SonTruong Nov 20 2020 at 11:59

To interesujące pytanie, podzielę się przemyśleniami na ten temat i podam rozwiązanie.

Terminologia:

Typ blokady aplikacji: ogólna nazwa kodu PIN / kodu PIN / hasła / kodu dostępu itp. (W poniższej sekcji użyję nazwy pin do zademonstrowania)

PinActivity: ekran, na którym użytkownicy wprowadzają swój kod PIN, aby się zweryfikować

Fabuła:

W przypadku aplikacji, które wymagają od użytkowników wprowadzenia kodu PIN, zwykle chcą mieć pewność, że poufne informacje nie zostaną ujawnione / skradzione przez inne osoby. Więc podzielimy działania aplikacji na 2 grupy.

  • Normalne działania: nie zawiera żadnych poufnych informacji, zwykle przed zalogowaniem się użytkowników do aplikacji, takich jak SplashActivity, LoginActivity, RegistrationActivity, PinActivity itp.

  • Działania zabezpieczone: zawierają poufne informacje, zwykle po zalogowaniu się użytkowników, takie jak MainActivity, HomeActivity, UserInfoActivity itp.

Warunki:

W przypadku działań zabezpieczonych musimy upewnić się, że użytkownicy zawsze wprowadzają swój kod PIN przed wyświetleniem zawartości, pokazując PinActivity. Ta aktywność zostanie pokazana w następujących scenariuszach:

  • [1] Gdy użytkownicy otwierają zabezpieczone działanie, tworzą normalne działanie, takie jak od SplashActivity do MainActivity

  • [2] Gdy użytkownicy otwierają zabezpieczoną aktywność, dotykając Powiadomień, na przykład gdy dotykają powiadomienia, aby otworzyć MainActivity

  • [3] Gdy użytkownicy dotykają aplikacji na ekranie Ostatnie

  • [4] Gdy aplikacja rozpoczyna zabezpieczoną aktywność z innego miejsca, takiego jak usługi, odbiornik transmisji itp.

Realizacja:

W przypadku [1] [2] i [4], przed rozpoczęciem zabezpieczonej czynności dodamy dodatkowy do pierwotnego celu. Utworzę plik o nazwie IntentUtils.kt

IntentUtils.kt

const val EXTRA_IS_PIN_REQUIRED = "EXTRA_IS_PIN_REQUIRED"

fun Intent.secured(): Intent {
    return this.apply {
        putExtra(EXTRA_IS_PIN_REQUIRED, true)
    }
}

Używaj tej klasy z normalnych czynności, powiadomień, usług itp.

startActivity(Intent(this, MainActivity::class.java).secured())

W przypadku [3] użyję 2 interfejsów API:

  • ProcessLifecycleOwner : aby wykryć, czy aplikacja działa w tle. Typowy scenariusz to kliknięcie przez użytkownika przycisku Home / Menu na swoich urządzeniach.

  • ActivityLifecycleCallbacks : Aby wykryć, czy działanie zostało wznowione, opierając się na metodzie onActivityResumed (activity) .

Najpierw tworzę aktywność podstawową, wszystkie normalne zapalenia muszą wykraczać poza tę klasę

BaseActivity.kt

open class BaseActivity : AppCompatActivity() {
    
    // This method indicates that a pin is required if 
    // users want to see the content inside.
    open fun isPinRequired() = false
}

Po drugie, tworzę zabezpieczone działanie, wszystkie zabezpieczone działania muszą pochodzić z tej klasy

SecuredActivity.kt

open class SecuredActivity : BaseActivity() {
    override fun isPinRequired() = true

    // This is useful when launch a secured activity with 
    // singleTop, singleTask, singleInstance launch mode
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        setIntent(intent)
    }
}

Po trzecie, tworzę klasę, która dziedziczy z aplikacji, cała logika znajduje się wewnątrz tej klasy

MyApplication.kt

class MyApplication : Application() {

    private var wasEnterBackground = false

    override fun onCreate() {
        super.onCreate()
        registerActivityLifecycleCallbacks(ActivityLifecycleCallbacksImpl())
        ProcessLifecycleOwner.get().lifecycle.addObserver(LifecycleObserverImpl())
    }

    private fun showPinActivity() {
        startActivity(Intent(this, PinActivity::class.java))
    }

    inner class LifecycleObserverImpl : LifecycleObserver {

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun onEnterBackground() {
            wasEnterBackground = true
        }
    }

    inner class ActivityLifecycleCallbacksImpl : ActivityLifecycleCallbacks {

        override fun onActivityResumed(activity: Activity) {
            val baseActivity = activity as BaseActivity
            if (!wasEnterBackground) {
                // Handle case [1] [2] and [4]
                val removed = removeIsPinRequiredKeyFromActivity(activity)
                if (removed) {
                    showPinActivity()
                }
            } else {
                // Handle case [3]
                wasEnterBackground = false
                if (baseActivity.isPinRequired()) {
                    removeIsPinRequiredKeyFromActivity(activity)
                    showPinActivity()
                }
            }
        }

        private fun removeIsPinRequiredKeyFromActivity(activity: Activity): Boolean {
            val key = EXTRA_IS_PIN_REQUIRED
            if (activity.intent.hasExtra(key)) {
                activity.intent.removeExtra(key)
                return true
            }
            return false
        }

        override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
        override fun onActivityStarted(activity: Activity) {}
        override fun onActivityPaused(activity: Activity) {}
        override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
        override fun onActivityStopped(activity: Activity) {}
        override fun onActivityDestroyed(activity: Activity) {}
    }
}

Wniosek:

To rozwiązanie działa w przypadkach, o których wspomniałem wcześniej, ale nie testowałem następujących scenariuszy:

  • Podczas uruchamiania zabezpieczone działanie ma tryb uruchamiania singleTop | singleTask | singleInstance
  • Gdy aplikacja zostaje zabita przez system przy małej ilości pamięci
  • Inne scenariusze, które ktoś może napotkać (jeśli tak, daj mi znać w sekcji komentarzy).