|
| 1 | +package dev.pranav.applock.features.lockscreen.ui |
| 2 | + |
| 3 | +import android.annotation.SuppressLint |
| 4 | +import android.content.Context |
| 5 | +import android.content.Intent |
| 6 | +import android.graphics.PixelFormat |
| 7 | +import android.view.KeyEvent |
| 8 | +import android.view.WindowManager |
| 9 | +import androidx.compose.ui.platform.ComposeView |
| 10 | +import androidx.lifecycle.* |
| 11 | +import androidx.savedstate.SavedStateRegistry |
| 12 | +import androidx.savedstate.SavedStateRegistryController |
| 13 | +import androidx.savedstate.SavedStateRegistryOwner |
| 14 | +import androidx.savedstate.setViewTreeSavedStateRegistryOwner |
| 15 | +import dev.pranav.applock.core.utils.appLockRepository |
| 16 | +import dev.pranav.applock.data.repository.PreferencesRepository |
| 17 | +import dev.pranav.applock.ui.theme.AppLockTheme |
| 18 | + |
| 19 | +@SuppressLint("ViewConstructor") |
| 20 | +class LockScreenOverlayManager(private val context: Context): |
| 21 | + LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner { |
| 22 | + |
| 23 | + private val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager |
| 24 | + private var composeView: ComposeView? = null |
| 25 | + |
| 26 | + // Lifecycle setup |
| 27 | + private val lifecycleRegistry = LifecycleRegistry(this) |
| 28 | + private val savedStateRegistryController = SavedStateRegistryController.create(this) |
| 29 | + private val store = ViewModelStore() |
| 30 | + private var isStateRestored = false |
| 31 | + |
| 32 | + override val lifecycle: Lifecycle get() = lifecycleRegistry |
| 33 | + override val savedStateRegistry: SavedStateRegistry get() = savedStateRegistryController.savedStateRegistry |
| 34 | + override val viewModelStore: ViewModelStore get() = store |
| 35 | + |
| 36 | + fun showOverlay( |
| 37 | + lockedPackageName: String, |
| 38 | + triggeringPackageName: String, |
| 39 | + onUnlock: () -> Unit |
| 40 | + ) { |
| 41 | + if (composeView != null) return |
| 42 | + |
| 43 | + if (!isStateRestored) { |
| 44 | + savedStateRegistryController.performRestore(null) |
| 45 | + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) |
| 46 | + isStateRestored = true |
| 47 | + } |
| 48 | + |
| 49 | + composeView = ComposeView(context).apply { |
| 50 | + setViewTreeLifecycleOwner(this@LockScreenOverlayManager) |
| 51 | + setViewTreeSavedStateRegistryOwner(this@LockScreenOverlayManager) |
| 52 | + setViewTreeViewModelStoreOwner(this@LockScreenOverlayManager) |
| 53 | + |
| 54 | + setContent { |
| 55 | + AppLockTheme { |
| 56 | + val appLockRepository = context.appLockRepository() |
| 57 | + val appName = try { |
| 58 | + val pm = context.packageManager |
| 59 | + pm.getApplicationLabel(pm.getApplicationInfo(lockedPackageName, 0)) |
| 60 | + .toString() |
| 61 | + } catch (_: Exception) { |
| 62 | + "App" |
| 63 | + } |
| 64 | + |
| 65 | + val onPinAttemptCallback = { pin: String -> |
| 66 | + val isValid = appLockRepository.validatePassword(pin) |
| 67 | + if (isValid) { |
| 68 | + onUnlock() |
| 69 | + removeOverlay() |
| 70 | + } |
| 71 | + isValid |
| 72 | + } |
| 73 | + |
| 74 | + val onPatternAttemptCallback = { pattern: String -> |
| 75 | + val isValid = appLockRepository.validatePattern(pattern) |
| 76 | + if (isValid) { |
| 77 | + onUnlock() |
| 78 | + removeOverlay() |
| 79 | + } |
| 80 | + isValid |
| 81 | + } |
| 82 | + |
| 83 | + val lockType = appLockRepository.getLockType() |
| 84 | + |
| 85 | + if (lockType == PreferencesRepository.LOCK_TYPE_PATTERN) { |
| 86 | + PatternLockScreen( |
| 87 | + fromMainActivity = false, |
| 88 | + lockedAppName = appName, |
| 89 | + triggeringPackageName = triggeringPackageName, |
| 90 | + onPatternAttempt = onPatternAttemptCallback |
| 91 | + ) |
| 92 | + } else { |
| 93 | + PasswordOverlayScreen( |
| 94 | + showBiometricButton = appLockRepository.isBiometricAuthEnabled(), |
| 95 | + fromMainActivity = false, |
| 96 | + lockedAppName = appName, |
| 97 | + triggeringPackageName = triggeringPackageName, |
| 98 | + onAuthSuccess = { |
| 99 | + onUnlock() |
| 100 | + removeOverlay() |
| 101 | + }, |
| 102 | + onBiometricAuth = { |
| 103 | + val intent = Intent( |
| 104 | + context, |
| 105 | + TransparentBiometricActivity::class.java |
| 106 | + ).apply { |
| 107 | + flags = |
| 108 | + Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_ANIMATION |
| 109 | + putExtra("locked_package", lockedPackageName) |
| 110 | + } |
| 111 | + context.startActivity(intent) |
| 112 | + }, |
| 113 | + onPinAttempt = onPinAttemptCallback |
| 114 | + ) |
| 115 | + } |
| 116 | + } |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + // Window Layout Parameters |
| 121 | + val params = WindowManager.LayoutParams( |
| 122 | + WindowManager.LayoutParams.MATCH_PARENT, |
| 123 | + WindowManager.LayoutParams.MATCH_PARENT, |
| 124 | + WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, |
| 125 | + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or |
| 126 | + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or |
| 127 | + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or |
| 128 | + WindowManager.LayoutParams.FLAG_SECURE, |
| 129 | + PixelFormat.TRANSLUCENT |
| 130 | + ).apply { |
| 131 | + // Respect brightness setting |
| 132 | + if (context.appLockRepository().shouldUseMaxBrightness()) { |
| 133 | + screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + composeView?.isFocusableInTouchMode = true |
| 138 | + composeView?.requestFocus() |
| 139 | + |
| 140 | + // Block Back Button |
| 141 | + composeView?.setOnKeyListener { _, keyCode, event -> |
| 142 | + if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) { |
| 143 | + val intent = Intent(Intent.ACTION_MAIN).apply { |
| 144 | + addCategory(Intent.CATEGORY_HOME) |
| 145 | + flags = Intent.FLAG_ACTIVITY_NEW_TASK |
| 146 | + } |
| 147 | + context.startActivity(intent) |
| 148 | + return@setOnKeyListener true |
| 149 | + } |
| 150 | + false |
| 151 | + } |
| 152 | + |
| 153 | + try { |
| 154 | + windowManager.addView(composeView, params) |
| 155 | + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START) |
| 156 | + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) |
| 157 | + } catch (e: Exception) { |
| 158 | + e.printStackTrace() |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + fun removeOverlay() { |
| 163 | + composeView?.let { |
| 164 | + try { |
| 165 | + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) |
| 166 | + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) |
| 167 | + windowManager.removeView(it) |
| 168 | + } catch (e: Exception) { |
| 169 | + e.printStackTrace() |
| 170 | + } |
| 171 | + composeView = null |
| 172 | + } |
| 173 | + } |
| 174 | +} |
0 commit comments