diff options
5 files changed, 129 insertions, 3 deletions
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 11c220b14bcc..0ec55f958f38 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -120,6 +120,7 @@ public class LockPatternView extends View { private static final String TAG = "LockPatternView"; private OnPatternListener mOnPatternListener; + private ExternalHapticsPlayer mExternalHapticsPlayer; @UnsupportedAppUsage private final ArrayList<Cell> mPattern = new ArrayList<Cell>(9); @@ -317,6 +318,13 @@ public class LockPatternView extends View { void onPatternDetected(List<Cell> pattern); } + /** An external haptics player for pattern updates. */ + public interface ExternalHapticsPlayer{ + + /** Perform haptic feedback when a cell is added to the pattern. */ + void performCellAddedFeedback(); + } + public LockPatternView(Context context) { this(context, null); } @@ -461,6 +469,15 @@ public class LockPatternView extends View { } /** + * Set the external haptics player for feedback on pattern detection. + * @param player The external player. + */ + @UnsupportedAppUsage + public void setExternalHapticsPlayer(ExternalHapticsPlayer player) { + mExternalHapticsPlayer = player; + } + + /** * Set the pattern explicitely (rather than waiting for the user to input * a pattern). * @param displayMode How to display the pattern. @@ -847,6 +864,16 @@ public class LockPatternView extends View { return null; } + @Override + public boolean performHapticFeedback(int feedbackConstant, int flags) { + if (mExternalHapticsPlayer != null) { + mExternalHapticsPlayer.performCellAddedFeedback(); + return true; + } else { + return super.performHapticFeedback(feedbackConstant, flags); + } + } + private void addCellToPattern(Cell newCell) { mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true; mPattern.add(newCell); diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt index e2bdc49d590c..bb152086cdab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt @@ -30,10 +30,12 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.res.R import com.android.systemui.statusbar.policy.DevicePostureController import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED +import com.android.systemui.testKosmos import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever @@ -89,6 +91,9 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { @Captor lateinit var postureCallbackCaptor: ArgumentCaptor<DevicePostureController.Callback> + private val kosmos = testKosmos() + private val msdlPlayer = kosmos.msdlPlayer + @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -112,7 +117,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardMessageAreaControllerFactory, mPostureController, fakeFeatureFlags, - mSelectedUserInteractor + mSelectedUserInteractor, + msdlPlayer, ) mKeyguardPatternView.onAttachedToWindow() } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index dd84bc6989a4..92e5432ad243 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -271,7 +271,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mLatencyTracker, mFalsingCollector, emergencyButtonController, mMessageAreaControllerFactory, - mDevicePostureController, mFeatureFlags, mSelectedUserInteractor); + mDevicePostureController, mFeatureFlags, mSelectedUserInteractor, + mMSDLPlayer); } else if (keyguardInputView instanceof KeyguardPasswordView) { return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index caa74780538e..f74d93e1d88d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -36,6 +36,7 @@ import com.android.internal.widget.LockPatternView.Cell; import com.android.internal.widget.LockscreenCredential; import com.android.keyguard.EmergencyButtonController.EmergencyButtonCallback; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.bouncer.ui.helper.BouncerHapticHelper; import com.android.systemui.classifier.FalsingClassifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; @@ -43,6 +44,8 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.user.domain.interactor.SelectedUserInteractor; +import com.google.android.msdl.domain.MSDLPlayer; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -67,6 +70,7 @@ public class KeyguardPatternViewController private LockPatternView mLockPatternView; private CountDownTimer mCountdownTimer; private AsyncTask<?, ?, ?> mPendingLockCheck; + private MSDLPlayer mMSDLPlayer; private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() { @Override @@ -75,6 +79,10 @@ public class KeyguardPatternViewController } }; + private final LockPatternView.ExternalHapticsPlayer mExternalHapticsPlayer = () -> { + BouncerHapticHelper.INSTANCE.playPatternDotFeedback(mMSDLPlayer, mView); + }; + /** * Useful for clearing out the wrong pattern after a delay */ @@ -166,6 +174,10 @@ public class KeyguardPatternViewController boolean isValidPattern) { boolean dismissKeyguard = mSelectedUserInteractor.getSelectedUserId() == userId; if (matched) { + BouncerHapticHelper.INSTANCE.playMSDLAuthenticationFeedback( + /* authenticationSucceeded= */true, + /* player =*/mMSDLPlayer + ); getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0); if (dismissKeyguard) { mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct); @@ -173,6 +185,10 @@ public class KeyguardPatternViewController getKeyguardSecurityCallback().dismiss(true, userId, SecurityMode.Pattern); } } else { + BouncerHapticHelper.INSTANCE.playMSDLAuthenticationFeedback( + /* authenticationSucceeded= */false, + /* player =*/mMSDLPlayer + ); mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); if (isValidPattern) { getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs); @@ -200,7 +216,7 @@ public class KeyguardPatternViewController EmergencyButtonController emergencyButtonController, KeyguardMessageAreaController.Factory messageAreaControllerFactory, DevicePostureController postureController, FeatureFlags featureFlags, - SelectedUserInteractor selectedUserInteractor) { + SelectedUserInteractor selectedUserInteractor, MSDLPlayer msdlPlayer) { super(view, securityMode, keyguardSecurityCallback, emergencyButtonController, messageAreaControllerFactory, featureFlags, selectedUserInteractor); mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -212,6 +228,7 @@ public class KeyguardPatternViewController featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)); mLockPatternView = mView.findViewById(R.id.lockPatternView); mPostureController = postureController; + mMSDLPlayer = msdlPlayer; } @Override @@ -249,6 +266,7 @@ public class KeyguardPatternViewController if (deadline != 0) { handleAttemptLockout(deadline); } + mLockPatternView.setExternalHapticsPlayer(mExternalHapticsPlayer); } @Override @@ -262,6 +280,7 @@ public class KeyguardPatternViewController cancelBtn.setOnClickListener(null); } mPostureController.removeCallback(mPostureCallback); + mLockPatternView.setExternalHapticsPlayer(null); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticHelper.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticHelper.kt new file mode 100644 index 000000000000..1faacff996ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticHelper.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.ui.helper + +import android.view.HapticFeedbackConstants +import android.view.View +import com.android.keyguard.AuthInteractionProperties +import com.android.systemui.Flags +//noinspection CleanArchitectureDependencyViolation: Data layer only referenced for this enum class +import com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.MSDLPlayer + +/** A helper object to deliver haptic feedback in bouncer interactions. */ +object BouncerHapticHelper { + + private val authInteractionProperties = AuthInteractionProperties() + + /** + * Deliver MSDL feedback as a result of authenticating through a bouncer. + * + * @param[authenticationSucceeded] Whether the authentication was successful or not. + * @param[player] The [MSDLPlayer] that delivers the correct feedback. + */ + fun playMSDLAuthenticationFeedback( + authenticationSucceeded: Boolean, + player: MSDLPlayer?, + ) { + if (player == null || !Flags.msdlFeedback()) { + return + } + + val token = + if (authenticationSucceeded) { + MSDLToken.UNLOCK + } else { + MSDLToken.FAILURE + } + player.playToken(token, authInteractionProperties) + } + + /** + * Deliver feedback when dragging through cells in the pattern bouncer. This function can play + * MSDL feedback using a [MSDLPlayer], or fallback to a default haptic feedback using the + * [View.performHapticFeedback] API and a [View]. + * + * @param[player] [MSDLPlayer] for MSDL feedback. + * @param[view] A [View] for default haptic feedback using [View.performHapticFeedback] + */ + fun playPatternDotFeedback(player: MSDLPlayer?, view: View?) { + if (player == null || !Flags.msdlFeedback()) { + view?.performHapticFeedback( + HapticFeedbackConstants.VIRTUAL_KEY, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING, + ) + } else { + player.playToken(MSDLToken.DRAG_INDICATOR) + } + } +} |