forked from LeddaZ/frameworks_base
240 lines
11 KiB
Markdown
240 lines
11 KiB
Markdown
# 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);
|
|
```
|