| # Falsing in SystemUI | |
| Phones are easily and often accidentally-activated in owners' pockets ("falsing" or "pocket | |
| dialing"). Because a phone's screen can be turned on with a single tap, and because we have further | |
| actions that be activated with basic tapping and swiping, it is critical that we | |
| analyze touch events on the screen for intentional vs accidental behavior. With analysis, | |
| features within SystemUI have an opportunity to ignore or even undo accidental interactions as they | |
| are occurring. | |
| ## Technical Details | |
| The `FalsingManager` tracks all touch interactions happening on a phone's lock screen. | |
| If you support any sort of touch gestures on the lock screen, you **must**, at a | |
| minimum, inform the `FalsingManager` of what touches are on touch targets vs not (things that may be | |
| intentional). If you do not tell the `FalsingManager`, it will assume touches on your feature are | |
| always accidental and penalize the session accordingly. | |
| Individual touch targets do not _have_ to be separated out; it's acceptable to | |
| wrap your whole feature in one virtual block that reports touches to the | |
| `FalsingManager`, however more granular tracking will result in better results | |
| across the whole lock screen. | |
| You can _act_ on the results of the `FalsingManager`. Instead of only telling | |
| the `FalsingManager` that touch events were on touch targets, you can further use the | |
| returned results to decide if you want to respond to an owner's touch, if you | |
| want to prompt them to confirm their action, or if you simply want to ignore the | |
| touch. | |
| The flow through the system looks like such: | |
| 1. Gesture on the screen. | |
| 2. The `FalsingManager` makes a note of all of the `MotionEvents`. | |
| * If no feature/touch target receives the `MotionEvents`, skip to 4. | |
| 3. Your touch target receives the `MotionEvents`. | |
| * Once your feature is ready to respond to the gesture in a substantive manner, it queries | |
| the `FalsingManager`. | |
| - Dragging animations, touch ripples, and other purely visual effects should not query. | |
| - Query once you are ready to launch a new feature or dialogue, or are otherwise going to | |
| change the state of the UI. | |
| - Generally, wait until `MotionEvent.ACTION_UP` to query or `View.OnClickListener#onClick`. | |
| - Only query once per gesture, at the end. | |
| * If the `FalsingManager` says it looks good, respond to the touch. | |
| 4. The `FalsingManager` checks to see if anyone queried about the gesture. If not, mark it as | |
| accidental. | |
| There is also an event fired by the `FalsingManager` that can be listened to by anyone, that | |
| indicates that the the `FalsingManager` believes the phone is actively being pocket-dialed. When | |
| fired, modal features, such as quick settings, keyguard bouncer, and others should retract | |
| themselves to prevent further pocket-dialing. | |
| ## Falsing "Belief" and History | |
| The `FalsingManager` maintains a recent history of false analyses. Using | |
| Bayesian statistics, it updates a "belief" in whether recent | |
| gestures are intentional or not. Any gesture that it is not explicitly queried about is treated as | |
| accidental, increasing the overall belief in | |
| false-iness. Gestures that are explicitly queried and that pass the relevant heuristics | |
| reduce belief that falsing is occurring. This information is tracked within the `HistoryTracker`. | |
| Changes in belief may influence internal heurstics within the `FalsingManager`, | |
| making it easier or harder for an owner to interact with their device. (An owner | |
| will always be able to interact with their device, but we may require double | |
| taps, or more deliberate swipes.) | |
| ## Responding to Touch Events | |
| The methods below inform the `FalsingManager` that a tap is occurring within an expected touch | |
| target. Match the methods with the gesture you expect the device owner to use. | |
| ### Single Tap | |
| `FalsingManager#isSimpleTape()`. This method | |
| performs a only very basic checking, checking that observed `MotionEvent`s are | |
| all within some small x & y region ("touch slop"). Useful for only the most simple of scenarios, | |
| you probably want `FalsingManager#isFalseTap` method for most cases. | |
| `FalsingManager#isFalseTap(@Penalty int penalty)`. This | |
| method tells the `FalsingManager` that you want to thoroughly validate a single tap. It | |
| returns true if it thinks the tap should be rejected (i.e. the tap looks more | |
| like a swipe) and false otherwise. | |
| It runs through the following heuristics to validate a tap: | |
| 1. If the device recognizes a face (i.e. face-auth) the tap is **accepted**. | |
| 2. If the tap is the _second_ tap in recent history and looks like a valid Double Tap | |
| the tap is **accepted**. This works exactly like `FalsingManager#isFalseDoubleTap`. | |
| 3. If the `HistoryTracker` reports strong belief in recent falsing, the tap is | |
| **rejected**. | |
| 4. Otherwise the tap is **accepted**. | |
| All the above rules are applied only after first confirming the gesture does | |
| in fact look like a simple tap. | |
| `penalty` is a measure of how much the `HistoryTracker`'s belief should be | |
| penalized in the event that the tap is rejected. This value is only used if | |
| the gesture fails to validate as a simple tap. | |
| The `@FalsingManager.Penalty` values are fairly straightforward, but note that you | |
| should generally be choosing `LOW_PENALTY`. It is inherently difficult to know if a | |
| tap is truly false or not, so a single mis-tap should apply only a small penalty. | |
| If the owner is further along in a UX flow, and is still mis-tapping, it may make more | |
| sense to increase the penalty as mis-taps should be less likely to occur after | |
| several successful gestures. | |
| ### Double Tap | |
| `FalsingManager#isFalseDoubleTap()`. This method tells the `FalsingManager` that | |
| your UI wants to validate a double tap. There are no parameters to pass to this method. | |
| Call this when you explicitly receive and want to verify a double tap, _not_ a single tap. | |
| Note that `FalsingManager#isFalseTap(boolean robustCheck, double falsePenalty)` | |
| will also check for double taps when `robustCheck` is set to true. If you are | |
| willing to use single taps, use that instead. | |
| ### Swipes and Other Gestures | |
| `FalsingManager#isFalseTouch(@Classifier.InteractionType int interactionType)`. | |
| Use this for any non-tap interactions. This includes expanding notifications, | |
| expanding quick settings, pulling up the bouncer, and more. You must pass | |
| the type of interaction you are evaluating when calling it. A large set of | |
| heuristics will be applied to analyze the gesture, and the exact rules vary depending upon | |
| the `InteractionType`. | |
| ### Ignoring A Gesture | |
| `FalsingCollector#avoidGesture()`. Tell the `FalsingManager` to pretend like the | |
| observed gesture never happened. **This method must be called when the observed | |
| `MotionEvent` is `MotionEvent.ACTION_DOWN`.** Attempting to call this method | |
| later in a gesture will not work. | |
| Notice that this method is actually a method on `FalsingCollector`. It is | |
| forcefully telling the `FalsingManager` to wholly pretend the gesture never | |
| happened. This is intended for security and PII sensitive gestures, such as | |
| password inputs. Please don't use this as a shortcut for avoiding the | |
| FalsingManager. Falsing works better the more behavior it is told about. | |
| ### Other Considerations | |
| Please try to call the `FalsingManager` only once per gesture. Wait until you | |
| are ready to act on the owner's action, and then query the `FalsingManager`. The `FalsingManager` | |
| will update its belief in pocket dialing based only on the last call made, so multiple calls per | |
| gesture are not well defined. | |
| The `FalsingManager` does not update its belief in pocket-dialing until after a gesture completes. | |
| That is to say, if the owner makes a bad tap on your feature, the "belief" in pocket dialing will | |
| not incorporate this new data after processing on the final `ACTION_UP` or `ACTION_CANCEL` event | |
| occurs. | |
| If you expect a mix of taps, double taps, and swipes on your feature, segment them | |
| accordingly. Figure out which `FalsingManager` method you need to call first, rather than relying | |
| on multiple calls to the `FalsingManager` to act as a sieve. | |
| Don't: | |
| ``` | |
| if (!mFalsingManager.isFalseTap(false, 0)) { | |
| // its a tap | |
| } else if (!mFalsingManager.isFalseTouch(GESTURE_A) { | |
| // do thing a | |
| } else if (!mFalsingManager.isFalseTouch(GESTURE_B) { | |
| // do thing b | |
| } else { | |
| // must be a false. | |
| } | |
| ``` | |
| Do: | |
| ``` | |
| void onTap() { | |
| if (!mFalsingManager.isFalseTap(false, 0)) { | |
| // its a tap | |
| } | |
| void onGestureA() { | |
| if (!mFalsingManager.isFalseTouch(GESTURE_A) { | |
| // do thing a | |
| } | |
| } | |
| void onGestureB() { | |
| if (!mFalsingManager.isFalseTouch(GESTURE_B) { | |
| // do thing b | |
| } | |
| } | |
| ``` | |
| ## Influencing Belief | |
| `FalsingCollector#updateFalseConfidence(FalsingClassifier.Result result)`. This | |
| method allows you to directly change the `FalsingManager`'s belief in the state | |
| of pocket dialing. If the owner does something unusual with their phone that you | |
| think indicates pocket dialing, you can call: | |
| ``` | |
| mFalsingCollector.updateFalseConfidence( | |
| FalsingClassifier.Result.falsed(0.6, "Owner is doing something fishy")); | |
| ``` | |
| A belief value of `1` indicates a 100% confidence of false behavior. A belief | |
| value of `0` would make no change in the `FalsingManager` and should be avoided | |
| as it simply creates noise in the logs. Generally, a middle value between the | |
| two extremes makes sense. | |
| A good example of where this is used is in the "Pattern" password input. We | |
| avoid recording those gestures in the `FalsingManager`, but we have the pattern input update | |
| the `FalsingManager` directly in some cases. If the owner simply taps on the pattern input, we | |
| record it as a false, (patterns are always 4 "cells" long, so single "cell" inputs are penalized). | |
| Conversely, if you think the owner does something that deserves a nice reward: | |
| ``` | |
| mFalsingCollector.updateFalseConfidence( | |
| FalsingClassifier.Result.passed(0.6)); | |
| ``` | |
| Again, useful on password inputs where the FalsingManager is avoiding recording | |
| the gesture. This is used on the "pin" password input, to recognize successful | |
| taps on the input buttons. | |
| ## Global Falsing Event | |
| If the `FalsingManager`'s belief in falsing crosses some internally defined | |
| threshold, it will fire an event that other parts of the system can listen for. | |
| This even indicates that the owner is likely actively pocket-dialing, and any | |
| currently open activities on the phone should retract themselves. | |
| To subscribe to this event, call | |
| `FalsingManager#addFalsingBeliefListener(FalsingBeliefListener listener)`. | |
| `FalsingBeliefListener` is a simple one method interface that will be called | |
| after when activities should retract themselves. | |
| **Do Listen For This**. Your code will work without it, but it is a handy, | |
| universal signal that will save the phone owner a lot of accidents. A simple | |
| implementation looks like: | |
| ``` | |
| mFalsingManager.addFalsingBeliefListener(MyFeatureClass::hide); | |
| ``` |