From fc546a96c4f3e345b3df4f61ec3f40ce1c4e9d83 Mon Sep 17 00:00:00 2001 From: Thales Lima Date: Thu, 10 Jun 2021 18:52:35 +0300 Subject: [PATCH] sysui: show security footer on the bottom when in split shade mode When showing the notification shade in split mode, the security footer should always be shown after the footer. This also updates resources to have a best usage of configs and dimensions. Fixes: 188906516 Test: Added tests to QSPanelTest Change-Id: Iea29490aa73bc4fb3fe3cce82e181a22adca2937 --- packages/SystemUI/res/values-land/dimens.xml | 4 +- .../SystemUI/res/values-sw600dp/config.xml | 2 + .../SystemUI/res/values-sw600dp/dimens.xml | 5 + .../src/com/android/systemui/qs/QSPanel.java | 41 +++-- .../systemui/qs/QSPanelController.java | 3 +- .../systemui/qs/QSPanelControllerBase.java | 2 +- .../com/android/systemui/qs/QSPanelTest.java | 115 ------------- .../com/android/systemui/qs/QSPanelTest.kt | 158 ++++++++++++++++++ 8 files changed, 190 insertions(+), 140 deletions(-) delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 34bf28aa741ea..ee0e4b34e9ff8 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -32,11 +32,11 @@ --> 48dp + @*android:dimen/quick_qs_offset_height 14dp - 0dp 12dp - 28dp 9dp 9dp diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index 2f5e8eaa22635..942cb2bcafa0d 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -38,4 +38,6 @@ 4 + + 1 diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index da80b85b38bf0..527537b6155e4 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -95,4 +95,9 @@ 24dp 80dp + + + 48dp + 0dp diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 7c7f566589199..f20a2db75f2e7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -42,7 +42,6 @@ import com.android.systemui.settings.brightness.BrightnessSlider; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; -import com.android.systemui.util.animation.UniqueObjectHostView; import java.util.ArrayList; import java.util.List; @@ -316,7 +315,6 @@ public class QSPanel extends LinearLayout implements Tunable { super.onConfigurationChanged(newConfig); mOnConfigurationChangedListeners.forEach( listener -> listener.onConfigurationChange(newConfig)); - switchSecurityFooter(); } @Override @@ -359,25 +357,21 @@ public class QSPanel extends LinearLayout implements Tunable { switchToParent(mFooter, parent, index); index++; } - - // The security footer is switched on orientation changes } - private void switchSecurityFooter() { - if (mSecurityFooter != null) { - if (mContext.getResources().getConfiguration().orientation - == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null) { - // Adding the security view to the header, that enables us to avoid scrolling - switchToParent(mSecurityFooter, mHeaderContainer, 0); - } else { - // Where should this go? If there's media, right before it. Otherwise, at the end. - View mediaView = findViewByPredicate(v -> v instanceof UniqueObjectHostView); - int index = -1; - if (mediaView != null) { - index = indexOfChild(mediaView); - } - switchToParent(mSecurityFooter, this, index); - } + /** Switch the security footer between top and bottom of QS depending on orientation. */ + public void switchSecurityFooter(boolean shouldUseSplitNotificationShade) { + if (mSecurityFooter == null) return; + + if (!shouldUseSplitNotificationShade + && mContext.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE && mHeaderContainer != null) { + // Adding the security view to the header, that enables us to avoid scrolling + switchToParent(mSecurityFooter, mHeaderContainer, 0); + } else { + // Add after the footer + int index = indexOfChild(mFooter); + switchToParent(mSecurityFooter, this, index + 1); } } @@ -652,9 +646,14 @@ public class QSPanel extends LinearLayout implements Tunable { return mListening; } - public void setSecurityFooter(View view) { + /** + * Set the security footer view and switch it into the right place + * @param view the view in question + * @param shouldUseSplitNotificationShade if QS is in split shade mode + */ + public void setSecurityFooter(View view, boolean shouldUseSplitNotificationShade) { mSecurityFooter = view; - switchSecurityFooter(); + switchSecurityFooter(shouldUseSplitNotificationShade); } protected void setPageMargin(int pageMargin) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index ae0f5104d20fe..7cbd45bd2af40 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -77,6 +77,7 @@ public class QSPanelController extends QSPanelControllerBase { refreshAllTiles(); } updateBrightnessMirror(); + mView.switchSecurityFooter(mShouldUseSplitNotificationShade); } }; @@ -141,7 +142,7 @@ public class QSPanelController extends QSPanelControllerBase { refreshAllTiles(); } mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener); - mView.setSecurityFooter(mQsSecurityFooter.getView()); + mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade); switchTileLayout(true); if (mBrightnessMirrorController != null) { mBrightnessMirrorController.addCallback(mBrightnessMirrorListener); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 7a0982688b498..c98550e196c1f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -69,7 +69,7 @@ public abstract class QSPanelControllerBase extends ViewContr private final DumpManager mDumpManager; private final FeatureFlags mFeatureFlags; protected final ArrayList mRecords = new ArrayList<>(); - private boolean mShouldUseSplitNotificationShade; + protected boolean mShouldUseSplitNotificationShade; @Nullable private Consumer mMediaVisibilityChangedListener; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java deleted file mode 100644 index 4f8859927d06a..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2017 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.qs; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.testing.TestableLooper.RunWithLooper; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.ActivityStarter; -import com.android.systemui.plugins.qs.QSTileView; -import com.android.systemui.qs.logging.QSLogger; -import com.android.systemui.qs.tileimpl.QSTileImpl; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Collections; - -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -@SmallTest -public class QSPanelTest extends SysuiTestCase { - - private TestableLooper mTestableLooper; - private QSPanel mQsPanel; - @Mock - private QSTileHost mHost; - @Mock - private QSTileImpl dndTile; - @Mock - private QSPanelControllerBase.TileRecord mDndTileRecord; - @Mock - private QSLogger mQSLogger; - private ViewGroup mParentView; - @Mock - private QSDetail.Callback mCallback; - @Mock - private QSTileView mQSTileView; - @Mock - private ActivityStarter mActivityStarter; - - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - mTestableLooper = TestableLooper.get(this); - -// // Dependencies for QSSecurityFooter -// mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter); -// mDependency.injectMockDependency(SecurityController.class); -// mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper()); -// mContext.addMockSystemService(Context.USER_SERVICE, mock(UserManager.class)); - mDndTileRecord.tile = dndTile; - mDndTileRecord.tileView = mQSTileView; - - mTestableLooper.runWithLooper(() -> { - mQsPanel = new QSPanel(mContext, null); - mQsPanel.initialize(); - mQsPanel.onFinishInflate(); - // Provides a parent with non-zero size for QSPanel - mParentView = new FrameLayout(mContext); - mParentView.addView(mQsPanel); - - when(dndTile.getTileSpec()).thenReturn("dnd"); - when(mHost.getTiles()).thenReturn(Collections.emptyList()); - when(mHost.createTileView(any(), any(), anyBoolean())).thenReturn(mQSTileView); - mQsPanel.addTile(mDndTileRecord); - mQsPanel.setCallback(mCallback); - }); - } - - @Test - public void testOpenDetailsWithExistingTile_NoException() { - mTestableLooper.processAllMessages(); - mQsPanel.openDetails(dndTile); - mTestableLooper.processAllMessages(); - - verify(mCallback).onShowingDetail(any(), anyInt(), anyInt()); - } - - @Test - public void testOpenDetailsWithNullParameter_NoException() { - mTestableLooper.processAllMessages(); - mQsPanel.openDetails(null); - mTestableLooper.processAllMessages(); - - verify(mCallback, never()).onShowingDetail(any(), anyInt(), anyInt()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt new file mode 100644 index 0000000000000..a83a5e1f5484e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2021 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.qs + +import android.content.res.Configuration +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QSTileView +import com.android.systemui.qs.QSPanelControllerBase.TileRecord +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +@SmallTest +class QSPanelTest : SysuiTestCase() { + private lateinit var mTestableLooper: TestableLooper + private lateinit var mQsPanel: QSPanel + + @Mock + private lateinit var mHost: QSTileHost + + @Mock + private lateinit var dndTile: QSTileImpl<*> + + @Mock + private lateinit var mDndTileRecord: TileRecord + + @Mock + private lateinit var mQSLogger: QSLogger + private lateinit var mParentView: ViewGroup + + @Mock + private lateinit var mCallback: QSDetail.Callback + + @Mock + private lateinit var mQSTileView: QSTileView + + @Before + @Throws(Exception::class) + fun setup() { + MockitoAnnotations.initMocks(this) + mTestableLooper = TestableLooper.get(this) + + mDndTileRecord.tile = dndTile + mDndTileRecord.tileView = mQSTileView + mTestableLooper.runWithLooper { + mQsPanel = QSPanel(mContext, null) + mQsPanel.initialize() + // QSPanel inflates a footer inside of it, mocking it here + mQsPanel.addView(LinearLayout(mContext).apply { id = R.id.qs_footer }) + mQsPanel.onFinishInflate() + mQsPanel.setSecurityFooter(View(mContext), false) + mQsPanel.setHeaderContainer(LinearLayout(mContext)) + // Provides a parent with non-zero size for QSPanel + mParentView = FrameLayout(mContext).apply { + addView(mQsPanel) + } + + whenever(dndTile.tileSpec).thenReturn("dnd") + whenever(mHost.tiles).thenReturn(emptyList()) + whenever(mHost.createTileView(any(), any(), anyBoolean())).thenReturn(mQSTileView) + mQsPanel.addTile(mDndTileRecord) + mQsPanel.setCallback(mCallback) + } + } + + @Test + fun testOpenDetailsWithExistingTile_NoException() { + mTestableLooper.runWithLooper { + mQsPanel.openDetails(dndTile) + } + + verify(mCallback).onShowingDetail(any(), anyInt(), anyInt()) + } + + @Test + fun testOpenDetailsWithNullParameter_NoException() { + mTestableLooper.runWithLooper { + mQsPanel.openDetails(null) + } + + verify(mCallback, never()).onShowingDetail(any(), anyInt(), anyInt()) + } + + @Test + fun testSecurityFooter_appearsOnBottomOnSplitShade() { + mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE)) + mQsPanel.switchSecurityFooter(true) + + mTestableLooper.runWithLooper { + mQsPanel.isExpanded = true + } + + assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(2) + } + + @Test + fun testSecurityFooter_appearsOnBottomIfPortrait() { + mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_PORTRAIT)) + mQsPanel.switchSecurityFooter(false) + + mTestableLooper.runWithLooper { + mQsPanel.isExpanded = true + } + + assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(2) + } + + @Test + fun testSecurityFooter_appearsOnTopIfSmallScreenAndLandscape() { + mQsPanel.onConfigurationChanged(getNewOrientationConfig(ORIENTATION_LANDSCAPE)) + mQsPanel.switchSecurityFooter(false) + + mTestableLooper.runWithLooper { + mQsPanel.isExpanded = true + } + + // -1 means that it is part of the mHeaderContainer + assertThat(mQsPanel.indexOfChild(mQsPanel.mSecurityFooter)).isEqualTo(-1) + } + + private fun getNewOrientationConfig(@Configuration.Orientation newOrientation: Int) = + context.resources.configuration.apply { orientation = newOrientation } +}