1 package com.android.systemui.biometrics
2 
3 import android.annotation.AnyThread
4 import android.annotation.MainThread
5 import android.util.Log
6 import com.android.systemui.dagger.qualifiers.Main
7 import com.android.systemui.shade.ShadeExpansionChangeEvent
8 import com.android.systemui.shade.ShadeExpansionStateManager
9 import java.util.concurrent.Executor
10 import javax.inject.Inject
11 
12 class AuthDialogPanelInteractionDetector
13 @Inject
14 constructor(
15     private val shadeExpansionStateManager: ShadeExpansionStateManager,
16     @Main private val mainExecutor: Executor,
17 ) {
18     private var action: Action? = null
19     private var panelState: Int = -1
20 
21     @MainThread
22     fun enable(onPanelInteraction: Runnable) {
23         if (action == null) {
24             action = Action(onPanelInteraction)
25             shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
26             shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
27         } else {
28             Log.e(TAG, "Already enabled")
29         }
30     }
31 
32     @MainThread
33     fun disable() {
34         if (action != null) {
35             Log.i(TAG, "Disable dectector")
36             action = null
37             panelState = -1
38             shadeExpansionStateManager.removeStateListener(this::onPanelStateChanged)
39             shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged)
40         }
41     }
42 
43     @AnyThread
44     private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
45         mainExecutor.execute {
46             action?.let {
47                 if (event.tracking || (event.expanded && event.fraction > 0 && panelState == 1)) {
48                     Log.i(TAG, "onPanelExpansionChanged, event: $event")
49                     it.onPanelInteraction.run()
50                     disable()
51                 }
52             }
53         }
54 
55     @AnyThread
56     private fun onPanelStateChanged(state: Int) =
57         mainExecutor.execute {
58             // When device owner set screen lock type as Swipe, and install work profile with
59             // pin/pattern/password & fingerprint or face, if work profile allow user to verify
60             // by BP, it is possible that BP will be displayed when keyguard is closing, in this
61             // case event.expanded = true and event.fraction > 0, so BP will be closed, adding
62             // panel state into consideration is workaround^2, this workaround works because
63             // onPanelStateChanged is earlier than onPanelExpansionChanged
64 
65             // we don't want to close BP in below case
66             //
67             // |      Action       |  tracking  |  expanded  |  fraction  |  panelState  |
68             // |      HeadsUp      |    NA      |     NA     |     NA     |      1       |
69             // |   b/285111529     |   false    |    true    |    > 0     |      2       |
70 
71             // Note: HeadsUp behavior was changed, so we can't got onPanelExpansionChanged now
72             panelState = state
73             Log.i(TAG, "onPanelStateChanged, state: $state")
74         }
75 }
76 
77 private data class Action(val onPanelInteraction: Runnable)
78 
79 private const val TAG = "AuthDialogPanelInteractionDetector"
80