Merge changes from topic "b172655679-bayes-falsing" into sc-dev

* changes:
  Add Falsing to the QS BrightnessSlider
  Add documentation for Falsing.
This commit is contained in:
Dave Mankoff
2021-03-30 01:03:44 +00:00
committed by Android (Google) Code Review
13 changed files with 397 additions and 27 deletions

View File

@@ -0,0 +1,240 @@
# 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#isFalseTap(boolean robustCheck, double falsePenalty)`. This
method tells the `FalsingManager` that you want to 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.
`robustCheck` determines what heuristics are used. If set to false, the method
performs a only very basic checking, checking that observed `MotionEvent`s are
all within some small x & y region ("touch slop").
When `robustCheck` is set to true, several more advanced rules are additionally
applied:
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 basic tap.
`falsePenalty` 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
`robustCheck` is set to true.
A value of `0` means no change in belief. A value of `1` means a _very_ strong
confidence in a false tap. In general, as a single tap on the screen is not
verifiable, a small value should be supplied - on the order of `0.1`. Pass `0`
if you don't want to penalize belief at all. Pass a higher value
the earlier in the UX flow your interaction occurs. Once an owner is farther
along in a UX flow (multiple taps or swipes), its safer to assume that a single
accidental tap should cause less of a penalty.
### 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 a new
gesture starts. 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 until the
following gesture begins.
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);
```

View File

@@ -37,6 +37,7 @@ public abstract class Classifier {
public static final int GENERIC = 7;
public static final int BOUNCER_UNLOCK = 8;
public static final int PULSE_EXPAND = 9;
public static final int BRIGHTNESS_SLIDER = 10;
@IntDef({
QUICK_SETTINGS,
@@ -48,7 +49,8 @@ public abstract class Classifier {
RIGHT_AFFORDANCE,
GENERIC,
BOUNCER_UNLOCK,
PULSE_EXPAND
PULSE_EXPAND,
BRIGHTNESS_SLIDER
})
@Retention(RetentionPolicy.SOURCE)
public @interface InteractionType {}

View File

@@ -148,6 +148,10 @@ class DistanceClassifier extends FalsingClassifier {
Result calculateFalsingResult(
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == Classifier.BRIGHTNESS_SLIDER) {
return Result.passed(0);
}
return !getPassedFlingThreshold() ? falsed(0.5, getReason()) : Result.passed(0.5);
}

View File

@@ -17,6 +17,7 @@
package com.android.systemui.classifier;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_PROXIMITY_PERCENT_COVERED_THRESHOLD;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import android.provider.DeviceConfig;
@@ -115,7 +116,7 @@ class ProximityClassifier extends FalsingClassifier {
Result calculateFalsingResult(
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == QUICK_SETTINGS) {
if (interactionType == QUICK_SETTINGS || interactionType == BRIGHTNESS_SLIDER) {
return Result.passed(0);
}

View File

@@ -18,6 +18,7 @@ package com.android.systemui.classifier;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
@@ -45,6 +46,7 @@ public class TypeClassifier extends FalsingClassifier {
boolean up = isUp();
boolean right = isRight();
double confidence = 1;
boolean wrongDirection = true;
switch (interactionType) {
case QUICK_SETTINGS:
@@ -52,6 +54,11 @@ public class TypeClassifier extends FalsingClassifier {
case NOTIFICATION_DRAG_DOWN:
wrongDirection = !vertical || up;
break;
case BRIGHTNESS_SLIDER:
confidence = 0; // Owners may return to original brightness.
// A more sophisticated thing to do here would be to look at the size of the
// vertical change relative to the screen size. _Some_ amount of vertical
// change should be expected.
case NOTIFICATION_DISMISS:
wrongDirection = vertical;
break;
@@ -70,7 +77,7 @@ public class TypeClassifier extends FalsingClassifier {
break;
}
return wrongDirection ? falsed(1, getReason(interactionType)) : Result.passed(0.5);
return wrongDirection ? falsed(confidence, getReason(interactionType)) : Result.passed(0.5);
}
private String getReason(int interactionType) {

View File

@@ -20,6 +20,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHT
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_X_SECONDARY_DEVIANCE;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import android.graphics.Point;
import android.provider.DeviceConfig;
@@ -87,6 +88,10 @@ class ZigZagClassifier extends FalsingClassifier {
Result calculateFalsingResult(
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
if (interactionType == BRIGHTNESS_SLIDER) {
return Result.passed(0);
}
List<MotionEvent> motionEvents = getRecentMotionEvents();
// Rotate horizontal gestures to be horizontal between their first and last point.
// Rotate vertical gestures to be vertical between their first and last point.

View File

@@ -27,7 +27,10 @@ import android.widget.SeekBar;
import androidx.annotation.Nullable;
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.util.ViewController;
@@ -42,9 +45,7 @@ import javax.inject.Inject;
*
* @see BrightnessMirrorController
*/
public class BrightnessSlider
extends ViewController<View>
implements ToggleSlider {
public class BrightnessSlider extends ViewController<View> implements ToggleSlider {
private Listener mListener;
private ToggleSlider mMirror;
@@ -52,15 +53,34 @@ public class BrightnessSlider
private BrightnessMirrorController mMirrorController;
private boolean mTracking;
private final boolean mUseMirror;
private final FalsingManager mFalsingManager;
private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mFalsingManager.isFalseTouch(Classifier.BRIGHTNESS_SLIDER);
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
return false;
}
};
BrightnessSlider(
View rootView,
BrightnessSliderView brightnessSliderView,
boolean useMirror
) {
boolean useMirror,
FalsingManager falsingManager) {
super(rootView);
mBrightnessSliderView = brightnessSliderView;
mUseMirror = useMirror;
mFalsingManager = falsingManager;
}
/**
@@ -78,6 +98,7 @@ public class BrightnessSlider
protected void onViewAttached() {
mBrightnessSliderView.setOnSeekBarChangeListener(mSeekListener);
mBrightnessSliderView.setOnCheckedChangeListener(mCheckListener);
mBrightnessSliderView.setOnInterceptListener(mOnInterceptListener);
}
@Override
@@ -85,6 +106,7 @@ public class BrightnessSlider
mBrightnessSliderView.setOnSeekBarChangeListener(null);
mBrightnessSliderView.setOnCheckedChangeListener(null);
mBrightnessSliderView.setOnDispatchTouchEventListener(null);
mBrightnessSliderView.setOnInterceptListener(null);
}
@Override
@@ -247,10 +269,12 @@ public class BrightnessSlider
public static class Factory {
BrightnessControllerSettings mSettings;
private final FalsingManager mFalsingManager;
@Inject
public Factory(BrightnessControllerSettings settings) {
public Factory(BrightnessControllerSettings settings, FalsingManager falsingManager) {
mSettings = settings;
mFalsingManager = falsingManager;
}
/**
@@ -270,7 +294,7 @@ public class BrightnessSlider
private BrightnessSlider fromTree(ViewGroup root, boolean useMirror) {
BrightnessSliderView v = root.requireViewById(R.id.brightness_slider);
return new BrightnessSlider(root, v, useMirror);
return new BrightnessSlider(root, v, useMirror, mFalsingManager);
}
/** Get the layout to inflate based on what slider to use */

View File

@@ -31,6 +31,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
/**
@@ -54,6 +55,7 @@ public class BrightnessSliderView extends FrameLayout {
private TextView mLabel;
private final CharSequence mText;
private DispatchTouchEventListener mListener;
private Gefingerpoken mOnInterceptListener;
public BrightnessSliderView(Context context) {
this(context, null);
@@ -105,6 +107,15 @@ public class BrightnessSliderView extends FrameLayout {
return super.dispatchTouchEvent(ev);
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
// We prevent disallowing on this view, but bubble it up to our parents.
// We need interception to handle falsing.
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
/**
* Attaches a listener to the {@link ToggleSeekBar} in the view so changes can be observed
* @param seekListener use {@code null} to remove listener
@@ -195,6 +206,18 @@ public class BrightnessSliderView extends FrameLayout {
return mSlider.getProgress();
}
public void setOnInterceptListener(Gefingerpoken onInterceptListener) {
mOnInterceptListener = onInterceptListener;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (mOnInterceptListener != null) {
return mOnInterceptListener.onInterceptTouchEvent(ev);
}
return super.onInterceptTouchEvent(ev);
}
/**
* Interface to attach a listener for {@link View#dispatchTouchEvent}.
*/

View File

@@ -16,6 +16,8 @@
package com.android.systemui.classifier;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.google.common.truth.Truth.assertThat;
import android.testing.AndroidTestingRunner;
@@ -96,13 +98,9 @@ public class DistanceClassifierTest extends ClassifierTest {
}
@Test
public void testPass_swipe() {
public void testPass_BrightnessSliderAlwaysPasses() {
mClassifier.onTouchEvent(appendDownEvent(1, 1));
assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isTrue();
mClassifier.onTouchEvent(appendMoveEvent(1, mDataProvider.getYdpi() * 3, 3));
mClassifier.onTouchEvent(appendUpEvent(1, mDataProvider.getYdpi() * 3, 300));
assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 1).isFalse())
.isFalse();
}
}

View File

@@ -16,11 +16,12 @@
package com.android.systemui.classifier;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.GENERIC;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
@@ -72,7 +73,7 @@ public class ProximityClassifierTest extends ClassifierTest {
public void testPass_uncovered() {
touchDown();
touchUp(10);
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(false));
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
@Test
@@ -81,7 +82,7 @@ public class ProximityClassifierTest extends ClassifierTest {
mClassifier.onProximityEvent(createSensorEvent(true, 1));
mClassifier.onProximityEvent(createSensorEvent(false, 2));
touchUp(20);
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(false));
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
@Test
@@ -90,7 +91,17 @@ public class ProximityClassifierTest extends ClassifierTest {
mClassifier.onProximityEvent(createSensorEvent(true, 1));
mClassifier.onProximityEvent(createSensorEvent(false, 11));
touchUp(10);
assertThat(mClassifier.classifyGesture(QUICK_SETTINGS, 0.5, 0).isFalse(), is(false));
assertThat(mClassifier.classifyGesture(QUICK_SETTINGS, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testPass_brightnessSlider() {
touchDown();
mClassifier.onProximityEvent(createSensorEvent(true, 1));
mClassifier.onProximityEvent(createSensorEvent(false, 11));
touchUp(10);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 0).isFalse())
.isFalse();
}
@Test
@@ -99,7 +110,7 @@ public class ProximityClassifierTest extends ClassifierTest {
mClassifier.onProximityEvent(createSensorEvent(true, 1));
mClassifier.onProximityEvent(createSensorEvent(false, 11));
touchUp(10);
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(true));
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
}
@Test
@@ -110,7 +121,7 @@ public class ProximityClassifierTest extends ClassifierTest {
mClassifier.onProximityEvent(createSensorEvent(true, 96));
mClassifier.onProximityEvent(createSensorEvent(false, 100));
touchUp(100);
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(true));
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isTrue();
}
@Test
@@ -120,7 +131,7 @@ public class ProximityClassifierTest extends ClassifierTest {
mClassifier.onProximityEvent(createSensorEvent(false, 11));
touchUp(10);
when(mDistanceClassifier.isLongSwipe()).thenReturn(mPassedResult);
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse(), is(false));
assertThat(mClassifier.classifyGesture(GENERIC, 0.5, 0).isFalse()).isFalse();
}
private void touchDown() {

View File

@@ -17,6 +17,7 @@
package com.android.systemui.classifier;
import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.android.systemui.classifier.Classifier.LEFT_AFFORDANCE;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
@@ -277,4 +278,46 @@ public class TypeClassifierTest extends ClassifierTest {
when(mDataProvider.isRight()).thenReturn(true);
assertThat(mClassifier.classifyGesture(RIGHT_AFFORDANCE, 0.5, 0).isFalse()).isTrue();
}
@Test
public void testPass_BrightnessSlider() {
when(mDataProvider.isVertical()).thenReturn(false);
when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
when(mDataProvider.isRight()).thenReturn(false);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(false);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isUp()).thenReturn(false);
when(mDataProvider.isRight()).thenReturn(true);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 0).isFalse()).isFalse();
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(true);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 0).isFalse()).isFalse();
}
@Test
public void testFalse_BrightnessSlider() {
when(mDataProvider.isVertical()).thenReturn(true);
when(mDataProvider.isUp()).thenReturn(false); // up and right should cause no effect.
when(mDataProvider.isRight()).thenReturn(false);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(false);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isUp()).thenReturn(false);
when(mDataProvider.isRight()).thenReturn(true);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 0).isFalse()).isTrue();
when(mDataProvider.isUp()).thenReturn(true);
when(mDataProvider.isRight()).thenReturn(true);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 0).isFalse()).isTrue();
}
}

View File

@@ -16,6 +16,8 @@
package com.android.systemui.classifier;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
import static com.google.common.truth.Truth.assertThat;
import android.testing.AndroidTestingRunner;
@@ -82,6 +84,13 @@ public class ZigZagClassifierTest extends ClassifierTest {
assertThat(mClassifier.classifyGesture(0, 0.5, 1).isFalse()).isFalse();
}
@Test
public void testPass_brightnessSliderAlwaysPasses() {
appendMoveEvent(0, 0);
appendMoveEvent(0, 100);
appendMoveEvent(0, 1);
assertThat(mClassifier.classifyGesture(BRIGHTNESS_SLIDER, 0.5, 1).isFalse()).isFalse();
}
@Test
public void testFail_minimumTouchesVertical() {

View File

@@ -25,6 +25,7 @@ import android.widget.SeekBar
import androidx.test.filters.SmallTest
import com.android.settingslib.RestrictedLockUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.statusbar.policy.BrightnessMirrorController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
@@ -75,6 +76,7 @@ class BrightnessSliderTest : SysuiTestCase() {
private lateinit var checkedChangeCaptor: ArgumentCaptor<CompoundButton.OnCheckedChangeListener>
@Mock
private lateinit var compoundButton: CompoundButton
private var mFalsingManager: FalsingManagerFake = FalsingManagerFake()
private lateinit var mController: BrightnessSlider
@@ -85,7 +87,7 @@ class BrightnessSliderTest : SysuiTestCase() {
whenever(mirrorController.toggleSlider).thenReturn(mirror)
whenever(motionEvent.copy()).thenReturn(motionEvent)
mController = BrightnessSlider(rootView, brightnessSliderView, true)
mController = BrightnessSlider(rootView, brightnessSliderView, true, mFalsingManager)
mController.init()
mController.setOnChangedListener(listener)
}
@@ -160,7 +162,8 @@ class BrightnessSliderTest : SysuiTestCase() {
@Test
fun testSettingMirrorWhenNotUseMirrorIsNoOp() {
val otherController = BrightnessSlider(rootView, brightnessSliderView, false)
val otherController = BrightnessSlider(
rootView, brightnessSliderView, false, mFalsingManager)
otherController.init()
otherController.setMirrorControllerAndMirror(mirrorController)