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

Commit 76ace72c authored by Behnam Heydarshahi's avatar Behnam Heydarshahi
Browse files

Move volume dialog away from the hinge

Bug: b/255341648
Test: atest VolumeDialogImplTest

Change-Id: Iff9d8506e2481a120995bde7eb847964f9d8c6bc
parent 95977e06
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -98,6 +98,8 @@ public class Events {
    public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8;
    public static final int DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED = 9;
    public static final int DISMISS_REASON_CSD_WARNING_TIMEOUT = 10;
    public static final int DISMISS_REASON_POSTURE_CHANGED = 11;

    public static final String[] DISMISS_REASONS = {
            "unknown",
            "touch_outside",
@@ -109,7 +111,8 @@ public class Events {
            "a11y_stream_changed",
            "output_chooser",
            "usb_temperature_below_threshold",
            "csd_warning_timeout"
            "csd_warning_timeout",
            "posture_changed"
    };

    public static final int SHOW_REASON_UNKNOWN = 0;
+65 −11
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;

import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL;
import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder;
import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED;
import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;

import android.animation.Animator;
@@ -129,6 +130,7 @@ import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.AlphaTintDrawableWrapper;
import com.android.systemui.util.DeviceConfigProxy;
@@ -184,7 +186,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
    private final boolean mChangeVolumeRowTintWhenInactive;

    private final Context mContext;
    private final H mHandler = new H();
    private final H mHandler;
    private final VolumeDialogController mController;
    private final DeviceProvisionedController mDeviceProvisionedController;
    private final Region mTouchableRegion = new Region();
@@ -259,16 +261,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
    private final AccessibilityManagerWrapper mAccessibilityMgr;
    private final Object mSafetyWarningLock = new Object();
    private final Accessibility mAccessibility = new Accessibility();

    private final ConfigurationController mConfigurationController;
    private final MediaOutputDialogFactory mMediaOutputDialogFactory;
    private final VolumePanelFactory mVolumePanelFactory;
    private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
    private final ActivityStarter mActivityStarter;

    private boolean mShowing;
    private boolean mShowA11yStream;

    private int mActiveStream;
    private int mPrevActiveStream;
    private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
@@ -300,6 +299,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
    @VisibleForTesting
    int mVolumeRingerMuteIconDrawableId;

    private int mOriginalGravity;
    private final DevicePostureController.Callback mDevicePostureControllerCallback;
    private final DevicePostureController mDevicePostureController;
    private @DevicePostureController.DevicePostureInt int mDevicePosture;
    private int mOrientation;

    public VolumeDialogImpl(
            Context context,
            VolumeDialogController volumeDialogController,
@@ -313,9 +318,12 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
            DeviceConfigProxy deviceConfigProxy,
            Executor executor,
            CsdWarningDialog.Factory csdWarningDialogFactory,
            DevicePostureController devicePostureController,
            Looper looper,
            DumpManager dumpManager) {
        mContext =
                new ContextThemeWrapper(context, R.style.volume_dialog_theme);
        mHandler = new H(looper);
        mController = volumeDialogController;
        mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -357,6 +365,16 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,

        initDimens();

        mOrientation = mContext.getResources().getConfiguration().orientation;
        mDevicePostureController = devicePostureController;
        if (mDevicePostureController != null) {
            int initialPosture = mDevicePostureController.getDevicePosture();
            mDevicePosture = initialPosture;
            mDevicePostureControllerCallback = this::onPostureChanged;
        } else {
            mDevicePostureControllerCallback = null;
        }

        mDeviceConfigProxy = deviceConfigProxy;
        mExecutor = executor;
        mSeparateNotification = mDeviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
@@ -364,6 +382,25 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
        updateRingerModeIconSet();
    }

    /**
     * Adjust the dialog location on the screen in order to avoid drawing on the hinge.
     */
    private void adjustPositionOnScreen() {
        final boolean isPortrait = mOrientation == Configuration.ORIENTATION_PORTRAIT;
        final boolean isHalfOpen =
                mDevicePosture == DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
        final boolean isTabletop = isPortrait && isHalfOpen;
        WindowManager.LayoutParams lp =  mWindow.getAttributes();
        int gravity = isTabletop ? (mOriginalGravity | Gravity.TOP) : mOriginalGravity;
        mWindowGravity = Gravity.getAbsoluteGravity(gravity,
                mContext.getResources().getConfiguration().getLayoutDirection());
        lp.gravity = mWindowGravity;
    }

    @VisibleForTesting int getWindowGravity() {
        return mWindowGravity;
    }

    /**
     * If ringer and notification are the same stream (T and earlier), use notification-like bell
     * icon set.
@@ -419,6 +456,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,

        mDeviceConfigProxy.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
                mExecutor, this::onDeviceConfigChange);

        if (mDevicePostureController != null) {
            mDevicePostureController.addCallback(mDevicePostureControllerCallback);
        }
    }

    @Override
@@ -427,6 +468,9 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
        mHandler.removeCallbacksAndMessages(null);
        mConfigurationController.removeCallback(this);
        mDeviceConfigProxy.removeOnPropertiesChangedListener(this::onDeviceConfigChange);
        if (mDevicePostureController != null) {
            mDevicePostureController.removeCallback(mDevicePostureControllerCallback);
        }
    }

    /**
@@ -441,7 +485,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
                mSeparateNotification = newVal;
                updateRingerModeIconSet();
                updateRingRowIcon();

            }
        }
    }
@@ -500,7 +543,6 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,

    private void initDialog(int lockTaskModeState) {
        mDialog = new CustomDialog(mContext);

        initDimens();

        mConfigurableTexts = new ConfigurableTexts(mContext);
@@ -524,14 +566,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
        lp.setTitle(VolumeDialogImpl.class.getSimpleName());
        lp.windowAnimations = -1;

        mWindowGravity = Gravity.getAbsoluteGravity(
                mContext.getResources().getInteger(R.integer.volume_dialog_gravity),
        mOriginalGravity = mContext.getResources().getInteger(R.integer.volume_dialog_gravity);
        mWindowGravity = Gravity.getAbsoluteGravity(mOriginalGravity,
                mContext.getResources().getConfiguration().getLayoutDirection());
        lp.gravity = mWindowGravity;

        mWindow.setAttributes(lp);
        mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT);

        mDialog.setContentView(R.layout.volume_dialog);
        mDialogView = mDialog.findViewById(R.id.volume_dialog);
        mDialogView.setAlpha(0);
@@ -1539,8 +1580,10 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
            animator.translationX(
                    (isWindowGravityLeft() ? -1 : 1) * mDialogView.getWidth() / 2.0f);
        }

        animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS,
                mDialogHideAnimationDurationMs)).start();

        checkODICaptionsTooltip(true);
        synchronized (mSafetyWarningLock) {
            if (mSafetyWarning != null) {
@@ -2237,6 +2280,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
        mTopContainer.setBackground(background);
    }

    @Override
    public void onConfigChanged(Configuration config) {
        mOrientation = config.orientation;
    }

    private final VolumeDialogController.Callbacks mControllerCallbackH
            = new VolumeDialogController.Callbacks() {
        @Override
@@ -2313,6 +2361,11 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
        }
    };

    @VisibleForTesting void onPostureChanged(int posture) {
        dismiss(DISMISS_REASON_POSTURE_CHANGED);
        mDevicePosture = posture;
    }

    private final class H extends Handler {
        private static final int SHOW = 1;
        private static final int DISMISS = 2;
@@ -2323,8 +2376,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
        private static final int STATE_CHANGED = 7;
        private static final int CSD_TIMEOUT = 8;

        public H() {
            super(Looper.getMainLooper());
        H(Looper looper) {
            super(looper);
        }

        @Override
@@ -2370,6 +2423,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
        protected void onStart() {
            super.setCanceledOnTouchOutside(true);
            super.onStart();
            adjustPositionOnScreen();
        }

        @Override
+5 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.systemui.volume.dagger;

import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;

import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.dagger.qualifiers.Main;
@@ -28,6 +29,7 @@ import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.volume.CsdWarningDialog;
@@ -42,7 +44,6 @@ import dagger.Provides;

import java.util.concurrent.Executor;


/** Dagger Module for code in the volume package. */
@Module
public interface VolumeModule {
@@ -65,6 +66,7 @@ public interface VolumeModule {
            DeviceConfigProxy deviceConfigProxy,
            @Main Executor executor,
            CsdWarningDialog.Factory csdFactory,
            DevicePostureController devicePostureController,
            DumpManager dumpManager) {
        VolumeDialogImpl impl = new VolumeDialogImpl(
                context,
@@ -79,6 +81,8 @@ public interface VolumeModule {
                deviceConfigProxy,
                executor,
                csdFactory,
                devicePostureController,
                Looper.getMainLooper(),
                dumpManager);
        impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
        impl.setAutomute(true);
+185 −4
Original line number Diff line number Diff line
@@ -17,22 +17,28 @@
package com.android.systemui.volume;

import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN;
import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN;
import static com.android.systemui.volume.VolumeDialogControllerImpl.STREAMS;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.app.KeyguardManager;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
@@ -53,7 +59,9 @@ import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.FakeConfigurationController;
import com.android.systemui.util.DeviceConfigProxyFake;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -82,6 +90,9 @@ public class VolumeDialogImplTest extends SysuiTestCase {
    View mDrawerNormal;
    private DeviceConfigProxyFake mDeviceConfigProxy;
    private FakeExecutor mExecutor;
    private TestableLooper mTestableLooper;
    private ConfigurationController mConfigurationController;
    private int mOriginalOrientation;

    @Mock
    VolumeDialogController mVolumeDialogController;
@@ -92,8 +103,6 @@ public class VolumeDialogImplTest extends SysuiTestCase {
    @Mock
    DeviceProvisionedController mDeviceProvisionedController;
    @Mock
    ConfigurationController mConfigurationController;
    @Mock
    MediaOutputDialogFactory mMediaOutputDialogFactory;
    @Mock
    VolumePanelFactory mVolumePanelFactory;
@@ -104,6 +113,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
    @Mock
    private DumpManager mDumpManager;
    @Mock CsdWarningDialog mCsdWarningDialog;
    @Mock
    DevicePostureController mPostureController;

    private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
            new CsdWarningDialog.Factory() {
@@ -119,9 +130,17 @@ public class VolumeDialogImplTest extends SysuiTestCase {

        getContext().addMockSystemService(KeyguardManager.class, mKeyguard);

        mTestableLooper = TestableLooper.get(this);
        mDeviceConfigProxy = new DeviceConfigProxyFake();
        mExecutor = new FakeExecutor(new FakeSystemClock());

        when(mPostureController.getDevicePosture())
                .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);

        mOriginalOrientation = mContext.getResources().getConfiguration().orientation;

        mConfigurationController = new FakeConfigurationController();

        mDialog = new VolumeDialogImpl(
                getContext(),
                mVolumeDialogController,
@@ -135,8 +154,9 @@ public class VolumeDialogImplTest extends SysuiTestCase {
                mDeviceConfigProxy,
                mExecutor,
                mCsdWarningDialogFactory,
                mDumpManager
            );
                mPostureController,
                mTestableLooper.getLooper(),
                mDumpManager);
        mDialog.init(0, null);
        State state = createShellState();
        mDialog.onStateChangedH(state);
@@ -227,6 +247,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
                ArgumentCaptor.forClass(VolumeDialogController.Callbacks.class);
        verify(mVolumeDialogController).addCallback(controllerCallbackCapture.capture(), any());
        VolumeDialogController.Callbacks callbacks = controllerCallbackCapture.getValue();

        callbacks.onShowSafetyWarning(AudioManager.FLAG_SHOW_UI);
        verify(mAccessibilityMgr).getRecommendedTimeoutMillis(
                VolumeDialogImpl.DIALOG_SAFETYWARNING_TIMEOUT_MILLIS,
@@ -371,11 +392,171 @@ public class VolumeDialogImplTest extends SysuiTestCase {
        verify(mCsdWarningDialog).show();
    }

    @Test
    public void ifPortraitHalfOpen_drawVerticallyTop() {
        DevicePostureController devicePostureController = mock(DevicePostureController.class);
        when(devicePostureController.getDevicePosture())
                .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);

        VolumeDialogImpl dialog = new VolumeDialogImpl(
                getContext(),
                mVolumeDialogController,
                mAccessibilityMgr,
                mDeviceProvisionedController,
                mConfigurationController,
                mMediaOutputDialogFactory,
                mVolumePanelFactory,
                mActivityStarter,
                mInteractionJankMonitor,
                mDeviceConfigProxy,
                mExecutor,
                mCsdWarningDialogFactory,
                devicePostureController,
                mTestableLooper.getLooper(),
                mDumpManager
        );
        dialog.init(0 , null);

        verify(devicePostureController).addCallback(any());
        dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
        mTestableLooper.processAllMessages(); // let dismiss() finish

        setOrientation(Configuration.ORIENTATION_PORTRAIT);

        // Call show() to trigger layout updates before verifying position
        dialog.show(SHOW_REASON_UNKNOWN);
        mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect

        int gravity = dialog.getWindowGravity();
        assertEquals(Gravity.TOP, gravity & Gravity.VERTICAL_GRAVITY_MASK);
    }

    @Test
    public void ifPortraitAndOpen_drawCenterVertically() {
        DevicePostureController devicePostureController = mock(DevicePostureController.class);
        when(devicePostureController.getDevicePosture())
                .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);

        VolumeDialogImpl dialog = new VolumeDialogImpl(
                getContext(),
                mVolumeDialogController,
                mAccessibilityMgr,
                mDeviceProvisionedController,
                mConfigurationController,
                mMediaOutputDialogFactory,
                mVolumePanelFactory,
                mActivityStarter,
                mInteractionJankMonitor,
                mDeviceConfigProxy,
                mExecutor,
                mCsdWarningDialogFactory,
                devicePostureController,
                mTestableLooper.getLooper(),
                mDumpManager
        );
        dialog.init(0, null);

        verify(devicePostureController).addCallback(any());
        dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_OPENED);
        mTestableLooper.processAllMessages(); // let dismiss() finish

        setOrientation(Configuration.ORIENTATION_PORTRAIT);

        dialog.show(SHOW_REASON_UNKNOWN);
        mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect

        int gravity = dialog.getWindowGravity();
        assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
    }

    @Test
    public void ifLandscapeAndHalfOpen_drawCenterVertically() {
        DevicePostureController devicePostureController = mock(DevicePostureController.class);
        when(devicePostureController.getDevicePosture())
                .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED);

        VolumeDialogImpl dialog = new VolumeDialogImpl(
                getContext(),
                mVolumeDialogController,
                mAccessibilityMgr,
                mDeviceProvisionedController,
                mConfigurationController,
                mMediaOutputDialogFactory,
                mVolumePanelFactory,
                mActivityStarter,
                mInteractionJankMonitor,
                mDeviceConfigProxy,
                mExecutor,
                mCsdWarningDialogFactory,
                devicePostureController,
                mTestableLooper.getLooper(),
                mDumpManager
        );
        dialog.init(0, null);

        verify(devicePostureController).addCallback(any());
        dialog.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED);
        mTestableLooper.processAllMessages(); // let dismiss() finish

        setOrientation(Configuration.ORIENTATION_LANDSCAPE);

        dialog.show(SHOW_REASON_UNKNOWN);
        mTestableLooper.processAllMessages(); // let show() finish before assessing its side-effect

        int gravity = dialog.getWindowGravity();
        assertEquals(Gravity.CENTER_VERTICAL, gravity & Gravity.VERTICAL_GRAVITY_MASK);
    }

    @Test
    public void dialogInit_addsPostureControllerCallback() {
        // init is already called in setup
        verify(mPostureController).addCallback(any());
    }

    @Test
    public void dialogDestroy_removesPostureControllerCallback() {
        VolumeDialogImpl dialog = new VolumeDialogImpl(
                getContext(),
                mVolumeDialogController,
                mAccessibilityMgr,
                mDeviceProvisionedController,
                mConfigurationController,
                mMediaOutputDialogFactory,
                mVolumePanelFactory,
                mActivityStarter,
                mInteractionJankMonitor,
                mDeviceConfigProxy,
                mExecutor,
                mCsdWarningDialogFactory,
                mPostureController,
                mTestableLooper.getLooper(),
                mDumpManager
        );
        dialog.init(0, null);

        verify(mPostureController, never()).removeCallback(any());

        dialog.destroy();

        verify(mPostureController).removeCallback(any());
    }

    private void setOrientation(int orientation) {
        Configuration config = new Configuration();
        config.orientation = orientation;
        if (mConfigurationController != null) {
            mConfigurationController.onConfigurationChanged(config);
        }
    }

    @After
    public void teardown() {
        if (mDialog != null) {
            mDialog.clearInternalHandlerAfterTest();
        }
        setOrientation(mOriginalOrientation);
        mTestableLooper.processAllMessages();
        reset(mPostureController);
    }

/*