Merge "Fix races in GHBM surface creation and usage" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-06-25 18:08:19 +00:00
committed by Android (Google) Code Review
4 changed files with 135 additions and 89 deletions

View File

@@ -71,6 +71,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import java.util.Optional;
@@ -97,6 +98,7 @@ public class UdfpsController implements DozeReceiver {
private static final long MIN_TOUCH_LOG_INTERVAL = 50;
private final Context mContext;
private final Execution mExecution;
private final FingerprintManager mFingerprintManager;
@NonNull private final LayoutInflater mInflater;
private final WindowManager mWindowManager;
@@ -496,6 +498,7 @@ public class UdfpsController implements DozeReceiver {
@Inject
public UdfpsController(@NonNull Context context,
@NonNull Execution execution,
@NonNull LayoutInflater inflater,
@Nullable FingerprintManager fingerprintManager,
@NonNull WindowManager windowManager,
@@ -514,6 +517,7 @@ public class UdfpsController implements DozeReceiver {
@Nullable Vibrator vibrator,
@NonNull Optional<UdfpsHbmProvider> hbmProvider) {
mContext = context;
mExecution = execution;
// TODO (b/185124905): inject main handler and vibrator once done prototyping
mMainHandler = new Handler(Looper.getMainLooper());
mVibrator = vibrator;
@@ -820,8 +824,8 @@ public class UdfpsController implements DozeReceiver {
mIsAodInterruptActive = false;
}
// This method can be called from the UI thread.
private void onFingerDown(int x, int y, float minor, float major) {
mExecution.assertIsMainThread();
if (mView == null) {
Log.w(TAG, "Null view in onFingerDown");
return;
@@ -835,8 +839,8 @@ public class UdfpsController implements DozeReceiver {
});
}
// This method can be called from the UI thread.
private void onFingerUp() {
mExecution.assertIsMainThread();
mActivePointerId = -1;
mGoodCaptureReceived = false;
mMainHandler.removeCallbacks(mAcquiredVibration);

View File

@@ -23,43 +23,36 @@ import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType;
/**
* Under-display fingerprint sensor Surface View. The surface should be used for HBM-specific things
* only. All other animations should be done on the other view.
* Surface View for providing the Global High-Brightness Mode (GHBM) illumination for UDFPS.
*/
public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator {
public class UdfpsSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "UdfpsSurfaceView";
private static final String SETTING_HBM_TYPE =
"com.android.systemui.biometrics.UdfpsSurfaceView.hbmType";
private static final @HbmType int DEFAULT_HBM_TYPE = UdfpsHbmTypes.GLOBAL_HBM;
/**
* This is used instead of {@link android.graphics.drawable.Drawable}, because the latter has
* several abstract methods that are not used here but require implementation.
* Notifies {@link UdfpsView} when to enable GHBM illumination.
*/
private interface SimpleDrawable {
void draw(Canvas canvas);
interface GhbmIlluminationListener {
/**
* @param surface the surface for which GHBM should be enabled.
* @param onIlluminatedRunnable a runnable that should be run after GHBM is enabled.
*/
void enableGhbm(@NonNull Surface surface, @Nullable Runnable onIlluminatedRunnable);
}
@NonNull private final SurfaceHolder mHolder;
@NonNull private final Paint mSensorPaint;
@NonNull private final SimpleDrawable mIlluminationDotDrawable;
private final int mOnIlluminatedDelayMs;
private final @HbmType int mHbmType;
@NonNull private RectF mSensorRect;
@Nullable private UdfpsHbmProvider mHbmProvider;
@Nullable private GhbmIlluminationListener mGhbmIlluminationListener;
@Nullable private Runnable mOnIlluminatedRunnable;
boolean mAwaitingSurfaceToStartIllumination;
boolean mHasValidSurface;
public UdfpsSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -71,82 +64,77 @@ public class UdfpsSurfaceView extends SurfaceView implements UdfpsIlluminator {
setZOrderOnTop(true);
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setFormat(PixelFormat.RGBA_8888);
mSensorRect = new RectF();
mSensorPaint = new Paint(0 /* flags */);
mSensorPaint.setAntiAlias(true);
mSensorPaint.setARGB(255, 255, 255, 255);
mSensorPaint.setStyle(Paint.Style.FILL);
}
mIlluminationDotDrawable = canvas -> {
canvas.drawOval(mSensorRect, mSensorPaint);
};
mOnIlluminatedDelayMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_udfps_illumination_transition_ms);
if (Build.IS_ENG || Build.IS_USERDEBUG) {
mHbmType = Settings.Secure.getIntForUser(mContext.getContentResolver(),
SETTING_HBM_TYPE, DEFAULT_HBM_TYPE, UserHandle.USER_CURRENT);
} else {
mHbmType = DEFAULT_HBM_TYPE;
@Override public void surfaceCreated(SurfaceHolder holder) {
mHasValidSurface = true;
if (mAwaitingSurfaceToStartIllumination) {
doIlluminate(mOnIlluminatedRunnable);
mOnIlluminatedRunnable = null;
mAwaitingSurfaceToStartIllumination = false;
}
}
@Override
public void setHbmProvider(@Nullable UdfpsHbmProvider hbmProvider) {
mHbmProvider = hbmProvider;
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// Unused.
}
@Override
public void startIllumination(@Nullable Runnable onIlluminatedRunnable) {
if (mHbmProvider != null) {
final Surface surface =
(mHbmType == UdfpsHbmTypes.GLOBAL_HBM) ? mHolder.getSurface() : null;
final Runnable onHbmEnabled = () -> {
if (mHbmType == UdfpsHbmTypes.GLOBAL_HBM) {
drawImmediately(mIlluminationDotDrawable);
}
if (onIlluminatedRunnable != null) {
// No framework API can reliably tell when a frame reaches the panel. A timeout
// is the safest solution.
postDelayed(onIlluminatedRunnable, mOnIlluminatedDelayMs);
} else {
Log.w(TAG, "startIllumination | onIlluminatedRunnable is null");
}
};
mHbmProvider.enableHbm(mHbmType, surface, onHbmEnabled);
} else {
Log.e(TAG, "startIllumination | mHbmProvider is null");
}
@Override public void surfaceDestroyed(SurfaceHolder holder) {
mHasValidSurface = false;
}
@Override
public void stopIllumination() {
if (mHbmProvider != null) {
final Runnable onHbmDisabled =
(mHbmType == UdfpsHbmTypes.GLOBAL_HBM) ? this::invalidate : null;
mHbmProvider.disableHbm(onHbmDisabled);
} else {
Log.e(TAG, "stopIllumination | mHbmProvider is null");
}
}
void onSensorRectUpdated(@NonNull RectF sensorRect) {
mSensorRect = sensorRect;
void setGhbmIlluminationListener(@Nullable GhbmIlluminationListener listener) {
mGhbmIlluminationListener = listener;
}
/**
* Immediately draws the provided drawable on this SurfaceView's surface.
* Note: there is no corresponding method to stop GHBM illumination. It is expected that
* {@link UdfpsView} will hide this view, which would destroy the surface and remove the
* illumination dot.
*/
private void drawImmediately(@NonNull SimpleDrawable drawable) {
void startGhbmIllumination(@Nullable Runnable onIlluminatedRunnable) {
if (mGhbmIlluminationListener == null) {
Log.e(TAG, "startIllumination | mGhbmIlluminationListener is null");
return;
}
if (mHasValidSurface) {
doIlluminate(onIlluminatedRunnable);
} else {
mAwaitingSurfaceToStartIllumination = true;
mOnIlluminatedRunnable = onIlluminatedRunnable;
}
}
private void doIlluminate(@Nullable Runnable onIlluminatedRunnable) {
if (mGhbmIlluminationListener == null) {
Log.e(TAG, "doIlluminate | mGhbmIlluminationListener is null");
return;
}
mGhbmIlluminationListener.enableGhbm(mHolder.getSurface(), onIlluminatedRunnable);
}
/**
* Immediately draws the illumination dot on this SurfaceView's surface.
*/
void drawIlluminationDot(@NonNull RectF sensorRect) {
if (!mHasValidSurface) {
Log.e(TAG, "drawIlluminationDot | the surface is destroyed or was never created.");
return;
}
Canvas canvas = null;
try {
canvas = mHolder.lockCanvas();
drawable.draw(canvas);
canvas.drawOval(sensorRect, mSensorPaint);
} finally {
// Make sure the surface is never left in a bad state.
if (canvas != null) {

View File

@@ -26,14 +26,19 @@ import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.biometrics.UdfpsHbmTypes.HbmType;
import com.android.systemui.doze.DozeReceiver;
/**
@@ -43,18 +48,25 @@ import com.android.systemui.doze.DozeReceiver;
public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIlluminator {
private static final String TAG = "UdfpsView";
private static final String SETTING_HBM_TYPE =
"com.android.systemui.biometrics.UdfpsSurfaceView.hbmType";
private static final @HbmType int DEFAULT_HBM_TYPE = UdfpsHbmTypes.LOCAL_HBM;
private static final int DEBUG_TEXT_SIZE_PX = 32;
@NonNull private final RectF mSensorRect;
@NonNull private final Paint mDebugTextPaint;
private final float mSensorTouchAreaCoefficient;
private final int mOnIlluminatedDelayMs;
private final @HbmType int mHbmType;
@NonNull private UdfpsSurfaceView mHbmSurfaceView;
// Only used for UdfpsHbmTypes.GLOBAL_HBM.
@Nullable private UdfpsSurfaceView mGhbmView;
// Can be different for enrollment, BiometricPrompt, Keyguard, etc.
@Nullable private UdfpsAnimationViewController mAnimationViewController;
// Used to obtain the sensor location.
@NonNull private FingerprintSensorPropertiesInternal mSensorProps;
private final float mSensorTouchAreaCoefficient;
@Nullable private UdfpsHbmProvider mHbmProvider;
@Nullable private String mDebugMessage;
private boolean mIlluminationRequested;
@@ -81,7 +93,15 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
mDebugTextPaint.setColor(Color.BLUE);
mDebugTextPaint.setTextSize(DEBUG_TEXT_SIZE_PX);
mIlluminationRequested = false;
mOnIlluminatedDelayMs = mContext.getResources().getInteger(
com.android.internal.R.integer.config_udfps_illumination_transition_ms);
if (Build.IS_ENG || Build.IS_USERDEBUG) {
mHbmType = Settings.Secure.getIntForUser(mContext.getContentResolver(),
SETTING_HBM_TYPE, DEFAULT_HBM_TYPE, UserHandle.USER_CURRENT);
} else {
mHbmType = DEFAULT_HBM_TYPE;
}
}
// Don't propagate any touch events to the child views.
@@ -93,7 +113,9 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
@Override
protected void onFinishInflate() {
mHbmSurfaceView = findViewById(R.id.hbm_view);
if (mHbmType == UdfpsHbmTypes.GLOBAL_HBM) {
mGhbmView = findViewById(R.id.hbm_view);
}
}
void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
@@ -102,7 +124,7 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
@Override
public void setHbmProvider(@Nullable UdfpsHbmProvider hbmProvider) {
mHbmSurfaceView.setHbmProvider(hbmProvider);
mHbmProvider = hbmProvider;
}
@Override
@@ -125,7 +147,6 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
2 * mSensorProps.sensorRadius + paddingX,
2 * mSensorProps.sensorRadius + paddingY);
mHbmSurfaceView.onSensorRectUpdated(new RectF(mSensorRect));
if (mAnimationViewController != null) {
mAnimationViewController.onSensorRectUpdated(new RectF(mSensorRect));
}
@@ -204,8 +225,32 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
if (mAnimationViewController != null) {
mAnimationViewController.onIlluminationStarting();
}
mHbmSurfaceView.setVisibility(View.VISIBLE);
mHbmSurfaceView.startIllumination(onIlluminatedRunnable);
if (mGhbmView != null) {
mGhbmView.setGhbmIlluminationListener(this::doIlluminate);
mGhbmView.setVisibility(View.VISIBLE);
mGhbmView.startGhbmIllumination(onIlluminatedRunnable);
} else {
doIlluminate(null /* surface */, onIlluminatedRunnable);
}
}
private void doIlluminate(@Nullable Surface surface, @Nullable Runnable onIlluminatedRunnable) {
if (mGhbmView != null && surface == null) {
Log.e(TAG, "doIlluminate | surface must be non-null for GHBM");
}
mHbmProvider.enableHbm(mHbmType, surface, () -> {
if (mGhbmView != null) {
mGhbmView.drawIlluminationDot(mSensorRect);
}
if (onIlluminatedRunnable != null) {
// No framework API can reliably tell when a frame reaches the panel. A timeout
// is the safest solution.
postDelayed(onIlluminatedRunnable, mOnIlluminatedDelayMs);
} else {
Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null");
}
});
}
@Override
@@ -214,7 +259,10 @@ public class UdfpsView extends FrameLayout implements DozeReceiver, UdfpsIllumin
if (mAnimationViewController != null) {
mAnimationViewController.onIlluminationStopped();
}
mHbmSurfaceView.setVisibility(View.INVISIBLE);
mHbmSurfaceView.stopIllumination();
if (mGhbmView != null) {
mGhbmView.setGhbmIlluminationListener(null);
mGhbmView.setVisibility(View.INVISIBLE);
}
mHbmProvider.disableHbm(null /* onHbmDisabled */);
}
}

View File

@@ -56,6 +56,8 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -75,7 +77,7 @@ import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@RunWithLooper(setAsMainLooper = true)
public class UdfpsControllerTest extends SysuiTestCase {
// Use this for inputs going into SystemUI. Use UdfpsController.mUdfpsSensorId for things
@@ -89,6 +91,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
private UdfpsController mUdfpsController;
// Dependencies
private Execution mExecution;
@Mock
private LayoutInflater mLayoutInflater;
@Mock
@@ -145,6 +148,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Before
public void setUp() {
setUpResources();
mExecution = new FakeExecution();
when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)).thenReturn(mUdfpsView);
final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
@@ -166,6 +171,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
mFgExecutor = new FakeExecutor(new FakeSystemClock());
mUdfpsController = new UdfpsController(
mContext,
mExecution,
mLayoutInflater,
mFingerprintManager,
mWindowManager,