Merge "sysui: show security footer on the bottom when in split shade mode" into sc-v2-dev
This commit is contained in:
@@ -32,11 +32,11 @@
|
||||
-->
|
||||
<dimen name="qs_customize_header_min_height">48dp</dimen>
|
||||
|
||||
<!-- In landscape the security footer is actually part of the header,
|
||||
and needs to be as short as the header -->
|
||||
<dimen name="qs_security_footer_single_line_height">@*android:dimen/quick_qs_offset_height</dimen>
|
||||
<dimen name="qs_footer_padding">14dp</dimen>
|
||||
<dimen name="qs_footers_margin_bottom">0dp</dimen>
|
||||
<dimen name="qs_security_footer_background_inset">12dp</dimen>
|
||||
<dimen name="qs_security_footer_corner_radius">28dp</dimen>
|
||||
|
||||
<dimen name="battery_detail_graph_space_top">9dp</dimen>
|
||||
<dimen name="battery_detail_graph_space_bottom">9dp</dimen>
|
||||
|
||||
@@ -38,4 +38,6 @@
|
||||
<!-- Max number of columns for quick controls area -->
|
||||
<integer name="controls_max_columns">4</integer>
|
||||
|
||||
<!-- How many lines to show in the security footer -->
|
||||
<integer name="qs_security_footer_maxLines">1</integer>
|
||||
</resources>
|
||||
|
||||
@@ -95,4 +95,9 @@
|
||||
<dimen name="controls_top_margin">24dp</dimen>
|
||||
|
||||
<dimen name="global_actions_grid_item_layout_height">80dp</dimen>
|
||||
|
||||
<!-- For large screens the security footer appears below the footer,
|
||||
same as phones in portrait -->
|
||||
<dimen name="qs_security_footer_single_line_height">48dp</dimen>
|
||||
<dimen name="qs_security_footer_background_inset">0dp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -77,6 +77,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
|
||||
refreshAllTiles();
|
||||
}
|
||||
updateBrightnessMirror();
|
||||
mView.switchSecurityFooter(mShouldUseSplitNotificationShade);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -141,7 +142,7 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
|
||||
refreshAllTiles();
|
||||
}
|
||||
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
|
||||
mView.setSecurityFooter(mQsSecurityFooter.getView());
|
||||
mView.setSecurityFooter(mQsSecurityFooter.getView(), mShouldUseSplitNotificationShade);
|
||||
switchTileLayout(true);
|
||||
if (mBrightnessMirrorController != null) {
|
||||
mBrightnessMirrorController.addCallback(mBrightnessMirrorListener);
|
||||
|
||||
@@ -69,7 +69,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
|
||||
private final DumpManager mDumpManager;
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
|
||||
private boolean mShouldUseSplitNotificationShade;
|
||||
protected boolean mShouldUseSplitNotificationShade;
|
||||
|
||||
@Nullable
|
||||
private Consumer<Boolean> mMediaVisibilityChangedListener;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
Reference in New Issue
Block a user