Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f6b248b0 authored by Juan Sebastian Martinez's avatar Juan Sebastian Martinez
Browse files

Introducing visuo-haptic effects for long-press on QS tiles.

To support a stronger mental model of which tiles can be long-pressed, a
dynamic haptic texture is delivered as the user performs the long-press
action on an eligible tile. A motion effect is paired with the haptic
feedback to show that the tile grows in size.

Test: SystemUiRoboTests:QSLongPressEffectTest
Flag: ACONFIG quick_settings_visual_haptics_longpress STAGING
Bug: 229856884

Change-Id: I2ade539b8b7e470299d9ec55c152cc34cf4f21a7
parent 095b77fc
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -337,6 +337,7 @@ constructor(
        if (ghostedView is LaunchableView) {
            // Restore the ghosted view visibility.
            ghostedView.setShouldBlockVisibilityChanges(false)
            ghostedView.onActivityLaunchAnimationEnd()
        } else {
            // Make the ghosted view visible. We ensure that the view is considered VISIBLE by
            // accessibility by first making it INVISIBLE then VISIBLE (see b/204944038#comment17
+3 −0
Original line number Diff line number Diff line
@@ -38,6 +38,9 @@ interface LaunchableView {
     * @param block whether we should block/postpone all calls to `setVisibility`.
     */
    fun setShouldBlockVisibilityChanges(block: Boolean)

    /** Perform an action when the activity launch animation ends */
    fun onActivityLaunchAnimationEnd() {}
}

/** A delegate that can be used by views to make the implementation of [LaunchableView] easier. */
+62 −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.haptics.qs

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.qs.tileimpl.QSTileViewImpl
import kotlinx.coroutines.launch

object QSLongPressEffectViewBinder {

    fun bind(
        tile: QSTileViewImpl,
        effect: QSLongPressEffect?,
    ) {
        if (effect == null) return

        tile.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                effect.scope = this

                launch {
                    effect.effectProgress.collect { progress ->
                        progress?.let {
                            if (it == 0f) {
                                tile.bringToFront()
                            }
                            tile.updateLongPressEffectProperties(it)
                        }
                    }
                }

                launch {
                    effect.actionType.collect { action ->
                        action?.let {
                            when (it) {
                                QSLongPressEffect.ActionType.CLICK -> tile.performClick()
                                QSLongPressEffect.ActionType.LONG_PRESS -> tile.performLongClick()
                            }
                            effect.clearActionType()
                        }
                    }
                }
            }
        }
    }
}
+5 −2
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags;
import com.android.systemui.settings.brightness.BrightnessController;
import com.android.systemui.settings.brightness.BrightnessMirrorHandler;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -90,9 +91,11 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
            FalsingManager falsingManager,
            StatusBarKeyguardViewManager statusBarKeyguardViewManager,
            SplitShadeStateController splitShadeStateController,
            SceneContainerFlags sceneContainerFlags) {
            SceneContainerFlags sceneContainerFlags,
            VibratorHelper vibratorHelper) {
        super(view, qsHost, qsCustomizerController, usingMediaPlayer, mediaHost,
                metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController);
                metricsLogger, uiEventLogger, qsLogger, dumpManager, splitShadeStateController,
                vibratorHelper);
        mTunerService = tunerService;
        mQsCustomizerController = qsCustomizerController;
        mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
+8 −2
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileViewImpl;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.animation.DisappearParameters;
@@ -87,6 +88,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr

    private SplitShadeStateController mSplitShadeStateController;

    private final VibratorHelper mVibratorHelper;

    @VisibleForTesting
    protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
            new QSPanel.OnConfigurationChangedListener() {
@@ -144,7 +147,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
            UiEventLogger uiEventLogger,
            QSLogger qsLogger,
            DumpManager dumpManager,
            SplitShadeStateController splitShadeStateController
            SplitShadeStateController splitShadeStateController,
            VibratorHelper vibratorHelper
    ) {
        super(view);
        mHost = host;
@@ -158,6 +162,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
        mSplitShadeStateController = splitShadeStateController;
        mShouldUseSplitNotificationShade =
                mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
        mVibratorHelper = vibratorHelper;
    }

    @Override
@@ -300,7 +305,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
    }

    private void addTile(final QSTile tile, boolean collapsedView) {
        final QSTileViewImpl tileView = new QSTileViewImpl(getContext(), collapsedView);
        final QSTileViewImpl tileView = new QSTileViewImpl(
                getContext(), collapsedView, mVibratorHelper);
        final TileRecord r = new TileRecord(tile, tileView);
        // TODO(b/250618218): Remove the QSLogger in QSTileViewImpl once we know the root cause of
        // b/250618218.
Loading