Add layout manager

Adds a layout manager that can make transitions.

Fixes: 283134494
Test: test LS with indication area and lock icon flags on.
Test: test with LS on different configurations, open, folded, landscape.

Change-Id: I31a47234b5c4b65c52074ca93ae4fe701649ecdb
This commit is contained in:
Aaron Liu
2023-06-14 11:00:48 -07:00
parent 14cef644c8
commit 02ec5b8bc0
11 changed files with 459 additions and 37 deletions

View File

@@ -32,7 +32,6 @@ import android.widget.ImageView;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.settingslib.Utils;
@@ -126,7 +125,6 @@ public class LockIconView extends FrameLayout implements Dumpable {
/**
* Set the location of the lock icon.
*/
@VisibleForTesting
public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) {
mLockIconCenter = center;
mRadius = radius;

View File

@@ -60,6 +60,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.model.TransitionStep;
@@ -387,15 +388,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private void updateLockIconLocation() {
final float scaleFactor = mAuthController.getScaleFactor();
final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
if (mUdfpsSupported) {
mView.setCenterLocation(mAuthController.getUdfpsLocation(),
mAuthController.getUdfpsRadius(), scaledPadding);
} else {
mView.setCenterLocation(
new Point((int) mWidthPixels / 2,
(int) (mHeightPixels
- ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
if (!mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
if (mUdfpsSupported) {
mView.setCenterLocation(mAuthController.getUdfpsLocation(),
mAuthController.getUdfpsRadius(), scaledPadding);
} else {
mView.setCenterLocation(
new Point((int) mWidthPixels / 2,
(int) (mHeightPixels
- ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))),
sLockIconRadiusPx * scaleFactor, scaledPadding);
}
}
}

View File

@@ -54,6 +54,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagsModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyguard.ui.view.layout.LockscreenLayoutModule;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.log.dagger.MonitorLog;
import com.android.systemui.log.table.TableLogBuffer;
@@ -178,6 +179,7 @@ import javax.inject.Named;
GarbageMonitorModule.class,
KeyboardModule.class,
LetterboxModule.class,
LockscreenLayoutModule.class,
LogModule.class,
MediaProjectionModule.class,
MotionToolModule.class,

View File

@@ -26,6 +26,7 @@ import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -42,6 +43,7 @@ constructor(
private val notificationShadeWindowView: NotificationShadeWindowView,
private val featureFlags: FeatureFlags,
private val indicationController: KeyguardIndicationController,
private val keyguardLayoutManager: KeyguardLayoutManager,
) : CoreStartable {
private var indicationAreaHandle: DisposableHandle? = null
@@ -51,6 +53,7 @@ constructor(
notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup
bindIndicationArea(notificationPanel)
bindLockIconView(notificationPanel)
keyguardLayoutManager.layoutViews()
}
fun bindIndicationArea(legacyParent: ViewGroup) {

View File

@@ -19,10 +19,7 @@ package com.android.systemui.keyguard.ui.view
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
import com.android.keyguard.LockIconView
import com.android.systemui.R
@@ -31,7 +28,7 @@ class KeyguardRootView(
context: Context,
private val attrs: AttributeSet?,
) :
FrameLayout(
ConstraintLayout(
context,
attrs,
) {
@@ -43,31 +40,11 @@ class KeyguardRootView(
private fun addIndicationTextArea() {
val view = KeyguardIndicationArea(context, attrs)
addView(
view,
FrameLayout.LayoutParams(
MATCH_PARENT,
WRAP_CONTENT,
)
.apply {
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
bottomMargin = R.dimen.keyguard_indication_margin_bottom.dp()
}
)
addView(view)
}
private fun addLockIconView() {
val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view }
addView(
view,
LayoutParams(
WRAP_CONTENT,
WRAP_CONTENT,
)
)
}
private fun Int.dp(): Int {
return context.resources.getDimensionPixelSize(this)
addView(view)
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright (C) 2023 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.keyguard.ui.view.layout
import android.content.Context
import android.graphics.Point
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import javax.inject.Inject
/**
* Positions elements of the lockscreen to the default position.
*
* This will be the most common use case for phones in portrait mode.
*/
@SysUISingleton
class DefaultLockscreenLayout
@Inject
constructor(
private val authController: AuthController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val windowManager: WindowManager,
private val context: Context,
) : LockscreenLayout {
override val id: String = DEFAULT
override fun layoutIndicationArea(rootView: KeyguardRootView) {
val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
rootView.getConstraintSet().apply {
constrainWidth(indicationArea.id, MATCH_PARENT)
constrainHeight(indicationArea.id, WRAP_CONTENT)
connect(
indicationArea.id,
BOTTOM,
PARENT_ID,
BOTTOM,
R.dimen.keyguard_indication_margin_bottom.dp()
)
connect(indicationArea.id, START, PARENT_ID, START)
connect(indicationArea.id, END, PARENT_ID, END)
applyTo(rootView)
}
}
override fun layoutLockIcon(rootView: KeyguardRootView) {
val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
val scaleFactor: Float = authController.scaleFactor
val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp()
val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp()
val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
val bounds = windowManager.currentWindowMetrics.bounds
val widthPixels = bounds.right.toFloat()
val heightPixels = bounds.bottom.toFloat()
val defaultDensity =
DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
DisplayMetrics.DENSITY_DEFAULT.toFloat()
val lockIconRadiusPx = (defaultDensity * 36).toInt()
if (isUdfpsSupported) {
authController.udfpsLocation?.let { udfpsLocation ->
centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView)
}
} else {
centerLockIcon(
Point(
(widthPixels / 2).toInt(),
(heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
),
lockIconRadiusPx * scaleFactor,
scaledPadding,
rootView
)
}
}
@VisibleForTesting
internal fun centerLockIcon(
center: Point,
radius: Float,
drawablePadding: Int,
rootView: KeyguardRootView,
) {
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return
lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding)
val sensorRect =
Rect().apply {
set(
center.x - radius.toInt(),
center.y - radius.toInt(),
center.x + radius.toInt(),
center.y + radius.toInt(),
)
}
rootView.getConstraintSet().apply {
constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left)
constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top)
connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top)
connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left)
applyTo(rootView)
}
}
private fun Int.dp(): Int {
return context.resources.getDimensionPixelSize(this)
}
private fun ConstraintLayout.getConstraintSet(): ConstraintSet {
val cs = ConstraintSet()
cs.clone(this)
return cs
}
companion object {
const val DEFAULT = "default"
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2023 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.keyguard.ui.view.layout
import android.content.res.Configuration
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
import com.android.systemui.statusbar.policy.ConfigurationController
import javax.inject.Inject
/**
* Manages layout changes for the lockscreen.
*
* To add a layout, add an entry to the map with a unique id and call #transitionToLayout(string).
*/
@SysUISingleton
class KeyguardLayoutManager
@Inject
constructor(
configurationController: ConfigurationController,
layouts: Set<@JvmSuppressWildcards LockscreenLayout>,
private val keyguardRootView: KeyguardRootView,
) {
internal val layoutIdMap: Map<String, LockscreenLayout> = layouts.associateBy { it.id }
private var layout: LockscreenLayout? = layoutIdMap[DEFAULT]
init {
configurationController.addCallback(
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
layoutViews()
}
}
)
}
/**
* Transitions to a layout.
*
* @param layoutId
* @return whether the transition has succeeded.
*/
fun transitionToLayout(layoutId: String): Boolean {
layout = layoutIdMap[layoutId] ?: return false
layoutViews()
return true
}
fun layoutViews() {
layout?.layoutViews(keyguardRootView)
}
companion object {
const val TAG = "KeyguardLayoutManager"
}
}
interface LockscreenLayout {
val id: String
fun layoutViews(rootView: KeyguardRootView) {
// Clear constraints.
ConstraintSet()
.apply {
clone(rootView)
knownIds.forEach { getConstraint(it).layout.copyFrom(ConstraintSet.Layout()) }
}
.applyTo(rootView)
layoutIndicationArea(rootView)
layoutLockIcon(rootView)
}
fun layoutIndicationArea(rootView: KeyguardRootView)
fun layoutLockIcon(rootView: KeyguardRootView)
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2023 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.keyguard.ui.view.layout
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@Module
abstract class LockscreenLayoutModule {
@Binds
@IntoSet
abstract fun bindDefaultLayout(
defaultLockscreenLayout: DefaultLockscreenLayout
): LockscreenLayout
}

View File

@@ -19,6 +19,7 @@ package com.android.keyguard;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR;
import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
@@ -143,6 +144,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase {
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(FACE_AUTH_REFACTOR, false);
mFeatureFlags.set(MIGRATE_LOCK_ICON, false);
mUnderTest = new LockIconViewController(
mLockIconView,
mStatusBarStateController,

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2023 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.keyguard.ui.view.layout
import android.graphics.Point
import android.view.ViewGroup
import android.view.WindowManager
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.monet.utils.ArgbSubject.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Answers
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@SmallTest
class DefaultLockscreenLayoutTest : SysuiTestCase() {
private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
private lateinit var rootView: KeyguardRootView
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
rootView = KeyguardRootView(context, null)
defaultLockscreenLayout =
DefaultLockscreenLayout(authController, keyguardUpdateMonitor, windowManager, context)
}
@Test
fun testLayoutViews_KeyguardIndicationArea() {
defaultLockscreenLayout.layoutViews(rootView)
val constraint = getViewConstraint(R.id.keyguard_indication_area)
assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT)
assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
}
@Test
fun testLayoutViews_lockIconView() {
defaultLockscreenLayout.layoutViews(rootView)
val constraint = getViewConstraint(R.id.lock_icon_view)
assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
}
@Test
fun testCenterLockIcon() {
defaultLockscreenLayout.centerLockIcon(Point(5, 6), 1F, 5, rootView)
val constraint = getViewConstraint(R.id.lock_icon_view)
assertThat(constraint.layout.mWidth).isEqualTo(2)
assertThat(constraint.layout.mHeight).isEqualTo(2)
assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.topMargin).isEqualTo(5)
assertThat(constraint.layout.startMargin).isEqualTo(4)
}
/** Get the ConstraintLayout constraint of the view. */
private fun getViewConstraint(viewId: Int): ConstraintSet.Constraint {
val constraintSet = ConstraintSet()
constraintSet.clone(rootView)
return constraintSet.getConstraint(viewId)
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2023 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.keyguard.ui.view.layout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@SmallTest
class KeyguardLayoutManagerTest : SysuiTestCase() {
private lateinit var keyguardLayoutManager: KeyguardLayoutManager
@Mock lateinit var configurationController: ConfigurationController
@Mock lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
@Mock lateinit var keyguardRootView: KeyguardRootView
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
whenever(defaultLockscreenLayout.id).thenReturn(DEFAULT)
keyguardLayoutManager =
KeyguardLayoutManager(
configurationController,
setOf(defaultLockscreenLayout),
keyguardRootView
)
}
@Test
fun testDefaultLayout() {
keyguardLayoutManager.transitionToLayout(DEFAULT)
verify(defaultLockscreenLayout).layoutViews(keyguardRootView)
}
@Test
fun testTransitionToLayout_validId() {
assertThat(keyguardLayoutManager.transitionToLayout(DEFAULT)).isTrue()
}
@Test
fun testTransitionToLayout_invalidId() {
assertThat(keyguardLayoutManager.transitionToLayout("abc")).isFalse()
}
}