Loading core/java/android/hardware/fingerprint/FingerprintManager.java +7 −6 Original line number Diff line number Diff line Loading @@ -923,14 +923,15 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerDown(int sensorId, int x, int y, float minor, float major) { public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { if (mService == null) { Slog.w(TAG, "onFingerDown: no fingerprint service"); return; } try { mService.onPointerDown(sensorId, x, y, minor, major); mService.onPointerDown(requestId, sensorId, x, y, minor, major); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading @@ -940,14 +941,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerUp(int sensorId) { public void onPointerUp(long requestId, int sensorId) { if (mService == null) { Slog.w(TAG, "onFingerDown: no fingerprint service"); return; } try { mService.onPointerUp(sensorId); mService.onPointerUp(requestId, sensorId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading @@ -957,14 +958,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onUiReady(int sensorId) { public void onUiReady(long requestId, int sensorId) { if (mService == null) { Slog.w(TAG, "onUiReady: no fingerprint service"); return; } try { mService.onUiReady(sensorId); mService.onUiReady(requestId, sensorId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading core/java/android/hardware/fingerprint/IFingerprintService.aidl +3 −3 Original line number Diff line number Diff line Loading @@ -155,13 +155,13 @@ interface IFingerprintService { void addAuthenticatorsRegisteredCallback(IFingerprintAuthenticatorsRegisteredCallback callback); // Notifies about a finger touching the sensor area. void onPointerDown(int sensorId, int x, int y, float minor, float major); void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major); // Notifies about a finger leaving the sensor area. void onPointerUp(int sensorId); void onPointerUp(long requestId, int sensorId); // Notifies about the fingerprint UI being ready (e.g. HBM illumination is enabled). void onUiReady(int sensorId); void onUiReady(long requestId, int sensorId); // Sets the controller for managing the UDFPS overlay. void setUdfpsOverlayController(in IUdfpsOverlayController controller); Loading core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -23,7 +23,7 @@ import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; */ oneway interface IUdfpsOverlayController { // Shows the overlay for the given sensor with a reason from BiometricOverlayConstants. void showUdfpsOverlay(int sensorId, int reason, IUdfpsOverlayControllerCallback callback); void showUdfpsOverlay(long requestId, int sensorId, int reason, IUdfpsOverlayControllerCallback callback); // Hides the overlay. void hideUdfpsOverlay(int sensorId); Loading packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +39 −20 Original line number Diff line number Diff line Loading @@ -49,7 +49,6 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.VelocityTracker; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; Loading Loading @@ -92,7 +91,7 @@ import kotlin.Unit; * Note that the current architecture is designed so that a single {@link UdfpsController} * controls/manages all UDFPS sensors. In other words, a single controller is registered with * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such * as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or * as {@link FingerprintManager#onPointerDown(long, int, int, int, float, float)} or * {@link IUdfpsOverlayController#showUdfpsOverlay} should all have * {@code sensorId} parameters. */ Loading Loading @@ -193,7 +192,7 @@ public class UdfpsController implements DozeReceiver { public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { @Override public void showUdfpsOverlay(int sensorId, int reason, public void showUdfpsOverlay(long requestId, int sensorId, int reason, @NonNull IUdfpsOverlayControllerCallback callback) { mFgExecutor.execute( () -> UdfpsController.this.showUdfpsOverlay(new UdfpsControllerOverlay( Loading @@ -203,8 +202,10 @@ public class UdfpsController implements DozeReceiver { mKeyguardUpdateMonitor, mDialogManager, mDumpManager, mLockscreenShadeTransitionController, mConfigurationController, mSystemClock, mKeyguardStateController, mUnlockedScreenOffAnimationController, mSensorProps, mHbmProvider, reason, callback, UdfpsController.this::onTouch, mUnlockedScreenOffAnimationController, mSensorProps, mHbmProvider, requestId, reason, callback, (view, event, fromUdfpsView) -> onTouch(requestId, event, fromUdfpsView), mActivityLaunchAnimator))); } Loading Loading @@ -318,7 +319,8 @@ public class UdfpsController implements DozeReceiver { if (mOverlay == null || mOverlay.isHiding()) { return false; } return onTouch(mOverlay.getOverlayView(), event, false); // TODO(b/225068271): may not be correct but no way to get the id yet return onTouch(mOverlay.getRequestId(), event, false); } /** Loading @@ -342,8 +344,18 @@ public class UdfpsController implements DozeReceiver { && getSensorLocation().contains(x, y); } private boolean onTouch(@NonNull View view, @NonNull MotionEvent event, boolean fromUdfpsView) { UdfpsView udfpsView = (UdfpsView) view; private boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { if (mOverlay == null) { Log.w(TAG, "ignoring onTouch with null overlay"); return false; } if (!mOverlay.matchesRequestId(requestId)) { Log.w(TAG, "ignoring stale touch event: " + requestId + " current: " + mOverlay.getRequestId()); return false; } final UdfpsView udfpsView = mOverlay.getOverlayView(); final boolean isIlluminationRequested = udfpsView.isIlluminationRequested(); boolean handled = false; switch (event.getActionMasked()) { Loading Loading @@ -453,7 +465,7 @@ public class UdfpsController implements DozeReceiver { // Do nothing to stay in portrait mode. } onFingerDown(x, y, minor, major); onFingerDown(requestId, x, y, minor, major); Log.v(TAG, "onTouch | finger down: " + touchInfo); mTouchLogTime = mSystemClock.elapsedRealtime(); mPowerManager.userActivity(mSystemClock.uptimeMillis(), Loading @@ -465,7 +477,7 @@ public class UdfpsController implements DozeReceiver { } } else { Log.v(TAG, "onTouch | finger outside"); onFingerUp(udfpsView); onFingerUp(requestId, udfpsView); } } Trace.endSection(); Loading @@ -482,7 +494,7 @@ public class UdfpsController implements DozeReceiver { } Log.v(TAG, "onTouch | finger up"); mAttemptedToDismissKeyguard = false; onFingerUp(udfpsView); onFingerUp(requestId, udfpsView); mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); Trace.endSection(); break; Loading Loading @@ -679,7 +691,7 @@ public class UdfpsController implements DozeReceiver { // Reset the controller back to its starting state. final UdfpsView oldView = mOverlay.getOverlayView(); if (oldView != null) { onFingerUp(oldView); onFingerUp(mOverlay.getRequestId(), oldView); } final boolean removed = mOverlay.hide(); if (mKeyguardViewManager.isShowingAlternateAuth()) { Loading Loading @@ -710,6 +722,8 @@ public class UdfpsController implements DozeReceiver { return; } // TODO(b/225068271): this may not be correct but there isn't a way to track it final long requestId = mOverlay != null ? mOverlay.getRequestId() : -1; mAodInterruptRunnable = () -> { mIsAodInterruptActive = true; // Since the sensor that triggers the AOD interrupt doesn't provide Loading @@ -719,10 +733,10 @@ public class UdfpsController implements DozeReceiver { mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelUdfps, AOD_INTERRUPT_TIMEOUT_MILLIS); // using a hard-coded value for major and minor until it is available from the sensor onFingerDown(screenX, screenY, minor, major); onFingerDown(requestId, screenX, screenY, minor, major); }; if (mScreenOn && mAodInterruptRunnable != null) { if (mScreenOn) { mAodInterruptRunnable.run(); mAodInterruptRunnable = null; } Loading Loading @@ -755,7 +769,7 @@ public class UdfpsController implements DozeReceiver { */ void onCancelUdfps() { if (mOverlay != null && mOverlay.getOverlayView() != null) { onFingerUp(mOverlay.getOverlayView()); onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView()); } if (!mIsAodInterruptActive) { return; Loading @@ -771,12 +785,17 @@ public class UdfpsController implements DozeReceiver { return mOnFingerDown; } private void onFingerDown(int x, int y, float minor, float major) { private void onFingerDown(long requestId, int x, int y, float minor, float major) { mExecution.assertIsMainThread(); if (mOverlay == null) { Log.w(TAG, "Null request in onFingerDown"); return; } if (!mOverlay.matchesRequestId(requestId)) { Log.w(TAG, "Mismatched fingerDown: " + requestId + " current: " + mOverlay.getRequestId()); return; } if (mOverlay.getAnimationViewController() instanceof UdfpsKeyguardViewController && !mStatusBarStateController.isDozing()) { Loading @@ -791,14 +810,14 @@ public class UdfpsController implements DozeReceiver { } } mOnFingerDown = true; mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major); mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major); Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); final UdfpsView view = mOverlay.getOverlayView(); if (view != null) { Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0); view.startIllumination(() -> { mFingerprintManager.onUiReady(mSensorProps.sensorId); mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId); mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0); }); Loading @@ -809,12 +828,12 @@ public class UdfpsController implements DozeReceiver { } } private void onFingerUp(@NonNull UdfpsView view) { private void onFingerUp(long requestId, @NonNull UdfpsView view) { mExecution.assertIsMainThread(); mActivePointerId = -1; mAcquiredReceived = false; if (mOnFingerDown) { mFingerprintManager.onPointerUp(mSensorProps.sensorId); mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); for (Callback cb : mCallbacks) { cb.onFingerUp(); } Loading packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +4 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ class UdfpsControllerOverlay( private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, private val sensorProps: FingerprintSensorPropertiesInternal, private var hbmProvider: UdfpsHbmProvider, val requestId: Long, @ShowReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, private val onTouch: (View, MotionEvent, Boolean) -> Boolean, Loading Loading @@ -276,6 +277,9 @@ class UdfpsControllerOverlay( } } /** Checks if the id is relevant for this overlay. */ fun matchesRequestId(id: Long): Boolean = requestId == -1L || requestId == id private fun WindowManager.LayoutParams.updateForLocation( location: SensorLocationInternal, animation: UdfpsAnimationViewController<*>? Loading Loading
core/java/android/hardware/fingerprint/FingerprintManager.java +7 −6 Original line number Diff line number Diff line Loading @@ -923,14 +923,15 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerDown(int sensorId, int x, int y, float minor, float major) { public void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major) { if (mService == null) { Slog.w(TAG, "onFingerDown: no fingerprint service"); return; } try { mService.onPointerDown(sensorId, x, y, minor, major); mService.onPointerDown(requestId, sensorId, x, y, minor, major); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading @@ -940,14 +941,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onPointerUp(int sensorId) { public void onPointerUp(long requestId, int sensorId) { if (mService == null) { Slog.w(TAG, "onFingerDown: no fingerprint service"); return; } try { mService.onPointerUp(sensorId); mService.onPointerUp(requestId, sensorId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading @@ -957,14 +958,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) public void onUiReady(int sensorId) { public void onUiReady(long requestId, int sensorId) { if (mService == null) { Slog.w(TAG, "onUiReady: no fingerprint service"); return; } try { mService.onUiReady(sensorId); mService.onUiReady(requestId, sensorId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } Loading
core/java/android/hardware/fingerprint/IFingerprintService.aidl +3 −3 Original line number Diff line number Diff line Loading @@ -155,13 +155,13 @@ interface IFingerprintService { void addAuthenticatorsRegisteredCallback(IFingerprintAuthenticatorsRegisteredCallback callback); // Notifies about a finger touching the sensor area. void onPointerDown(int sensorId, int x, int y, float minor, float major); void onPointerDown(long requestId, int sensorId, int x, int y, float minor, float major); // Notifies about a finger leaving the sensor area. void onPointerUp(int sensorId); void onPointerUp(long requestId, int sensorId); // Notifies about the fingerprint UI being ready (e.g. HBM illumination is enabled). void onUiReady(int sensorId); void onUiReady(long requestId, int sensorId); // Sets the controller for managing the UDFPS overlay. void setUdfpsOverlayController(in IUdfpsOverlayController controller); Loading
core/java/android/hardware/fingerprint/IUdfpsOverlayController.aidl +1 −1 Original line number Diff line number Diff line Loading @@ -23,7 +23,7 @@ import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; */ oneway interface IUdfpsOverlayController { // Shows the overlay for the given sensor with a reason from BiometricOverlayConstants. void showUdfpsOverlay(int sensorId, int reason, IUdfpsOverlayControllerCallback callback); void showUdfpsOverlay(long requestId, int sensorId, int reason, IUdfpsOverlayControllerCallback callback); // Hides the overlay. void hideUdfpsOverlay(int sensorId); Loading
packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +39 −20 Original line number Diff line number Diff line Loading @@ -49,7 +49,6 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.VelocityTracker; import android.view.View; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; Loading Loading @@ -92,7 +91,7 @@ import kotlin.Unit; * Note that the current architecture is designed so that a single {@link UdfpsController} * controls/manages all UDFPS sensors. In other words, a single controller is registered with * {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such * as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or * as {@link FingerprintManager#onPointerDown(long, int, int, int, float, float)} or * {@link IUdfpsOverlayController#showUdfpsOverlay} should all have * {@code sensorId} parameters. */ Loading Loading @@ -193,7 +192,7 @@ public class UdfpsController implements DozeReceiver { public class UdfpsOverlayController extends IUdfpsOverlayController.Stub { @Override public void showUdfpsOverlay(int sensorId, int reason, public void showUdfpsOverlay(long requestId, int sensorId, int reason, @NonNull IUdfpsOverlayControllerCallback callback) { mFgExecutor.execute( () -> UdfpsController.this.showUdfpsOverlay(new UdfpsControllerOverlay( Loading @@ -203,8 +202,10 @@ public class UdfpsController implements DozeReceiver { mKeyguardUpdateMonitor, mDialogManager, mDumpManager, mLockscreenShadeTransitionController, mConfigurationController, mSystemClock, mKeyguardStateController, mUnlockedScreenOffAnimationController, mSensorProps, mHbmProvider, reason, callback, UdfpsController.this::onTouch, mUnlockedScreenOffAnimationController, mSensorProps, mHbmProvider, requestId, reason, callback, (view, event, fromUdfpsView) -> onTouch(requestId, event, fromUdfpsView), mActivityLaunchAnimator))); } Loading Loading @@ -318,7 +319,8 @@ public class UdfpsController implements DozeReceiver { if (mOverlay == null || mOverlay.isHiding()) { return false; } return onTouch(mOverlay.getOverlayView(), event, false); // TODO(b/225068271): may not be correct but no way to get the id yet return onTouch(mOverlay.getRequestId(), event, false); } /** Loading @@ -342,8 +344,18 @@ public class UdfpsController implements DozeReceiver { && getSensorLocation().contains(x, y); } private boolean onTouch(@NonNull View view, @NonNull MotionEvent event, boolean fromUdfpsView) { UdfpsView udfpsView = (UdfpsView) view; private boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { if (mOverlay == null) { Log.w(TAG, "ignoring onTouch with null overlay"); return false; } if (!mOverlay.matchesRequestId(requestId)) { Log.w(TAG, "ignoring stale touch event: " + requestId + " current: " + mOverlay.getRequestId()); return false; } final UdfpsView udfpsView = mOverlay.getOverlayView(); final boolean isIlluminationRequested = udfpsView.isIlluminationRequested(); boolean handled = false; switch (event.getActionMasked()) { Loading Loading @@ -453,7 +465,7 @@ public class UdfpsController implements DozeReceiver { // Do nothing to stay in portrait mode. } onFingerDown(x, y, minor, major); onFingerDown(requestId, x, y, minor, major); Log.v(TAG, "onTouch | finger down: " + touchInfo); mTouchLogTime = mSystemClock.elapsedRealtime(); mPowerManager.userActivity(mSystemClock.uptimeMillis(), Loading @@ -465,7 +477,7 @@ public class UdfpsController implements DozeReceiver { } } else { Log.v(TAG, "onTouch | finger outside"); onFingerUp(udfpsView); onFingerUp(requestId, udfpsView); } } Trace.endSection(); Loading @@ -482,7 +494,7 @@ public class UdfpsController implements DozeReceiver { } Log.v(TAG, "onTouch | finger up"); mAttemptedToDismissKeyguard = false; onFingerUp(udfpsView); onFingerUp(requestId, udfpsView); mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); Trace.endSection(); break; Loading Loading @@ -679,7 +691,7 @@ public class UdfpsController implements DozeReceiver { // Reset the controller back to its starting state. final UdfpsView oldView = mOverlay.getOverlayView(); if (oldView != null) { onFingerUp(oldView); onFingerUp(mOverlay.getRequestId(), oldView); } final boolean removed = mOverlay.hide(); if (mKeyguardViewManager.isShowingAlternateAuth()) { Loading Loading @@ -710,6 +722,8 @@ public class UdfpsController implements DozeReceiver { return; } // TODO(b/225068271): this may not be correct but there isn't a way to track it final long requestId = mOverlay != null ? mOverlay.getRequestId() : -1; mAodInterruptRunnable = () -> { mIsAodInterruptActive = true; // Since the sensor that triggers the AOD interrupt doesn't provide Loading @@ -719,10 +733,10 @@ public class UdfpsController implements DozeReceiver { mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelUdfps, AOD_INTERRUPT_TIMEOUT_MILLIS); // using a hard-coded value for major and minor until it is available from the sensor onFingerDown(screenX, screenY, minor, major); onFingerDown(requestId, screenX, screenY, minor, major); }; if (mScreenOn && mAodInterruptRunnable != null) { if (mScreenOn) { mAodInterruptRunnable.run(); mAodInterruptRunnable = null; } Loading Loading @@ -755,7 +769,7 @@ public class UdfpsController implements DozeReceiver { */ void onCancelUdfps() { if (mOverlay != null && mOverlay.getOverlayView() != null) { onFingerUp(mOverlay.getOverlayView()); onFingerUp(mOverlay.getRequestId(), mOverlay.getOverlayView()); } if (!mIsAodInterruptActive) { return; Loading @@ -771,12 +785,17 @@ public class UdfpsController implements DozeReceiver { return mOnFingerDown; } private void onFingerDown(int x, int y, float minor, float major) { private void onFingerDown(long requestId, int x, int y, float minor, float major) { mExecution.assertIsMainThread(); if (mOverlay == null) { Log.w(TAG, "Null request in onFingerDown"); return; } if (!mOverlay.matchesRequestId(requestId)) { Log.w(TAG, "Mismatched fingerDown: " + requestId + " current: " + mOverlay.getRequestId()); return; } if (mOverlay.getAnimationViewController() instanceof UdfpsKeyguardViewController && !mStatusBarStateController.isDozing()) { Loading @@ -791,14 +810,14 @@ public class UdfpsController implements DozeReceiver { } } mOnFingerDown = true; mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major); mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major); Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0); final UdfpsView view = mOverlay.getOverlayView(); if (view != null) { Trace.beginAsyncSection("UdfpsController.e2e.startIllumination", 0); view.startIllumination(() -> { mFingerprintManager.onUiReady(mSensorProps.sensorId); mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId); mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE); Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0); }); Loading @@ -809,12 +828,12 @@ public class UdfpsController implements DozeReceiver { } } private void onFingerUp(@NonNull UdfpsView view) { private void onFingerUp(long requestId, @NonNull UdfpsView view) { mExecution.assertIsMainThread(); mActivePointerId = -1; mAcquiredReceived = false; if (mOnFingerDown) { mFingerprintManager.onPointerUp(mSensorProps.sensorId); mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId); for (Callback cb : mCallbacks) { cb.onFingerUp(); } Loading
packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +4 −0 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ class UdfpsControllerOverlay( private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, private val sensorProps: FingerprintSensorPropertiesInternal, private var hbmProvider: UdfpsHbmProvider, val requestId: Long, @ShowReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, private val onTouch: (View, MotionEvent, Boolean) -> Boolean, Loading Loading @@ -276,6 +277,9 @@ class UdfpsControllerOverlay( } } /** Checks if the id is relevant for this overlay. */ fun matchesRequestId(id: Long): Boolean = requestId == -1L || requestId == id private fun WindowManager.LayoutParams.updateForLocation( location: SensorLocationInternal, animation: UdfpsAnimationViewController<*>? Loading