Loading core/java/com/android/internal/widget/LockPatternView.java +27 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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); } Loading Loading @@ -460,6 +468,15 @@ public class LockPatternView extends View { mOnPatternListener = onPatternListener; } /** * 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). Loading Loading @@ -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); Loading packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +7 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -112,7 +117,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardMessageAreaControllerFactory, mPostureController, fakeFeatureFlags, mSelectedUserInteractor mSelectedUserInteractor, msdlPlayer, ) mKeyguardPatternView.onAttachedToWindow() } Loading packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +2 −1 Original line number Diff line number Diff line Loading @@ -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, Loading packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +20 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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 */ Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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; Loading @@ -212,6 +228,7 @@ public class KeyguardPatternViewController featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)); mLockPatternView = mView.findViewById(R.id.lockPatternView); mPostureController = postureController; mMSDLPlayer = msdlPlayer; } @Override Loading Loading @@ -249,6 +266,7 @@ public class KeyguardPatternViewController if (deadline != 0) { handleAttemptLockout(deadline); } mLockPatternView.setExternalHapticsPlayer(mExternalHapticsPlayer); } @Override Loading @@ -262,6 +280,7 @@ public class KeyguardPatternViewController cancelBtn.setOnClickListener(null); } mPostureController.removeCallback(mPostureCallback); mLockPatternView.setExternalHapticsPlayer(null); } @Override Loading packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticHelper.kt 0 → 100644 +73 −0 Original line number Diff line number Diff line /* * 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) } } } Loading
core/java/com/android/internal/widget/LockPatternView.java +27 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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); } Loading Loading @@ -460,6 +468,15 @@ public class LockPatternView extends View { mOnPatternListener = onPatternListener; } /** * 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). Loading Loading @@ -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); Loading
packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt +7 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -112,7 +117,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() { mKeyguardMessageAreaControllerFactory, mPostureController, fakeFeatureFlags, mSelectedUserInteractor mSelectedUserInteractor, msdlPlayer, ) mKeyguardPatternView.onAttachedToWindow() } Loading
packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +2 −1 Original line number Diff line number Diff line Loading @@ -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, Loading
packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +20 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading @@ -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 */ Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -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; Loading @@ -212,6 +228,7 @@ public class KeyguardPatternViewController featureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)); mLockPatternView = mView.findViewById(R.id.lockPatternView); mPostureController = postureController; mMSDLPlayer = msdlPlayer; } @Override Loading Loading @@ -249,6 +266,7 @@ public class KeyguardPatternViewController if (deadline != 0) { handleAttemptLockout(deadline); } mLockPatternView.setExternalHapticsPlayer(mExternalHapticsPlayer); } @Override Loading @@ -262,6 +280,7 @@ public class KeyguardPatternViewController cancelBtn.setOnClickListener(null); } mPostureController.removeCallback(mPostureCallback); mLockPatternView.setExternalHapticsPlayer(null); } @Override Loading
packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticHelper.kt 0 → 100644 +73 −0 Original line number Diff line number Diff line /* * 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) } } }