Implement 30sec lockout and fix cancellation
diff --git a/framework/server/com/libremobileos/faceunlock/server/FaceUnlockServer.java b/framework/server/com/libremobileos/faceunlock/server/FaceUnlockServer.java
index eaa1889..3968d39 100644
--- a/framework/server/com/libremobileos/faceunlock/server/FaceUnlockServer.java
+++ b/framework/server/com/libremobileos/faceunlock/server/FaceUnlockServer.java
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.face.V1_0.FaceError;
 import android.hardware.biometrics.face.V1_0.Feature;
 import android.hardware.biometrics.face.V1_0.Status;
+import android.os.CountDownTimer;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -73,6 +74,8 @@
 	private CameraService mCameraService;
 	private int mUserId = 0;
 	private String mStorePath = "/data/vendor_de/0/facedata";
+	private boolean isLocked = false;
+	private boolean isTimerTicking = false;
 
 	private final IBinder mFaceUnlockHalBinder = new IFaceHalService.Stub() {
 
@@ -191,9 +194,21 @@
 
 		@Override
 		public int cancel() {
+			if (DEBUG)
+				Log.d(TAG, "cancel");
+
 			// Not sure what to do here.
-			mCameraService.closeCamera();
-			mCameraService.stopBackgroundThread();
+			if (mCameraService != null) {
+				mCameraService.closeCamera();
+				mCameraService.stopBackgroundThread();
+			}
+			try {
+				mCallback.onError(kDeviceId, mUserId, FaceError.CANCELED, 0);
+			} catch (RemoteException e) {
+				e.printStackTrace();
+			}
+			isTimerTicking = false;
+			lockOutTimer.cancel();
 			return Status.OK;
 		}
 
@@ -250,11 +265,17 @@
 			if (DEBUG)
 				Log.d(TAG, "authenticate " + operationId);
 
-			mCameraService = new CameraService(mContext, faceCallback);
-			mWorkHandler.post(() -> {
-				mCameraService.startBackgroundThread();
-				mCameraService.openCamera();
-			});
+			if (!isLocked) {
+				mCameraService = new CameraService(mContext, faceCallback);
+				mWorkHandler.post(() -> {
+					mCameraService.startBackgroundThread();
+					mCameraService.openCamera();
+				});
+				if (!isTimerTicking) {
+					isTimerTicking = true;
+					lockOutTimer.start();
+				}
+			}
 			return Status.OK;
 		}
 
@@ -271,6 +292,10 @@
 			if (DEBUG)
 				Log.d(TAG, "resetLockout");
 
+			isLocked = false;
+			isTimerTicking = false;
+			lockOutTimer.cancel();
+
 			return Status.OK;
 		}
 	};
@@ -299,6 +324,10 @@
 		@Override
 		public void processImage (Size previewSize, Size rotatedSize, Bitmap rgbBitmap,int rotation)
 		{
+			if (isLocked) {
+				mCameraService.readyForNextImage();
+				return;
+			}
 			// No mutex needed as this method is not reentrant.
 			if (computingDetection) {
 				mCameraService.readyForNextImage();
@@ -337,6 +366,7 @@
 									// ignore the warning, api 33-only stuff right there :D
 									String base64hat = result.toString(StandardCharsets.UTF_8.name());
 									byte[] hat = Base64.decode(base64hat, Base64.URL_SAFE);
+									lockOutTimer.cancel();
 									mCallback.onAuthenticated(kDeviceId, kFaceId, mUserId, hat);
 								}
 							} catch (IOException e) {
@@ -355,6 +385,23 @@
 		}
 	};
 
+	CountDownTimer lockOutTimer = new CountDownTimer(30000, 1000) {
+		public void onTick(long millisUntilFinished) {
+			Log.d(TAG, "lockOutTimer: " + millisUntilFinished / 1000);
+			isTimerTicking = true;
+		}
+
+		public void onFinish() {
+			isLocked = true;
+			isTimerTicking = false;
+			try {
+				mCallback.onError(kDeviceId, mUserId, FaceError.TIMEOUT, 0);
+			} catch (RemoteException e) {
+				e.printStackTrace();
+			}
+		}
+	};
+
 	private class FaceHandler extends Handler {
 		public FaceHandler(Looper looper) {
 			super(looper);