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 4739a3f4c7d69..64ef5cfdf7e2a 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 }
+}