Merge "Support small clock for the lock screen preview" into udc-dev

This commit is contained in:
George Lin
2023-05-22 16:58:01 +00:00
committed by Android (Google) Code Review
8 changed files with 365 additions and 51 deletions

View File

@@ -96,6 +96,7 @@
<!-- additional offset for clock switch area items -->
<dimen name="small_clock_height">114dp</dimen>
<dimen name="small_clock_padding_top">28dp</dimen>
<dimen name="clock_padding_start">28dp</dimen>
<dimen name="below_clock_padding_start">32dp</dimen>
<dimen name="below_clock_padding_end">16dp</dimen>

View File

@@ -66,6 +66,17 @@ public class KeyguardClockSwitch extends RelativeLayout {
top + targetHeight);
}
/** Returns a region for the small clock to position itself, based on the given parent. */
public static Rect getSmallClockRegion(ViewGroup parent) {
int targetHeight = parent.getResources()
.getDimensionPixelSize(R.dimen.small_clock_text_size);
return new Rect(
parent.getLeft(),
parent.getTop(),
parent.getRight(),
parent.getTop() + targetHeight);
}
/**
* Frame for small/large clocks
*/
@@ -172,13 +183,8 @@ public class KeyguardClockSwitch extends RelativeLayout {
void updateClockTargetRegions() {
if (mClock != null) {
if (mSmallClockFrame.isLaidOut()) {
int targetHeight = getResources()
.getDimensionPixelSize(R.dimen.small_clock_text_size);
mClock.getSmallClock().getEvents().onTargetRegionChanged(new Rect(
mSmallClockFrame.getLeft(),
mSmallClockFrame.getTop(),
mSmallClockFrame.getRight(),
mSmallClockFrame.getTop() + targetHeight));
Rect targetRegion = getSmallClockRegion(mSmallClockFrame);
mClock.getSmallClock().getEvents().onTargetRegionChanged(targetRegion);
}
if (mLargeClockFrame.isLaidOut()) {

View File

@@ -0,0 +1,65 @@
/*
* 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.data.repository
import android.os.UserHandle
import android.provider.Settings
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext
@SysUISingleton
class KeyguardClockRepository
@Inject
constructor(
private val secureSettings: SecureSettings,
@Background private val backgroundDispatcher: CoroutineDispatcher,
) {
val selectedClockSize: Flow<SettingsClockSize> =
secureSettings
.observerFlow(
names = arrayOf(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK),
userId = UserHandle.USER_SYSTEM,
)
.onStart { emit(Unit) } // Forces an initial update.
.map { getClockSize() }
private suspend fun getClockSize(): SettingsClockSize {
return withContext(backgroundDispatcher) {
if (
secureSettings.getIntForUser(
Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
1,
UserHandle.USER_CURRENT
) == 1
) {
SettingsClockSize.DYNAMIC
} else {
SettingsClockSize.SMALL
}
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
/** Encapsulates business-logic related to the keyguard clock. */
@SysUISingleton
class KeyguardClockInteractor
@Inject
constructor(
repository: KeyguardClockRepository,
) {
val selectedClockSize: Flow<SettingsClockSize> = repository.selectedClockSize
}

View File

@@ -0,0 +1,23 @@
/*
* 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.shared.model
enum class SettingsClockSize {
DYNAMIC,
SMALL,
}

View File

@@ -0,0 +1,60 @@
/*
* 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.binder
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.flow.collect
/** Binder for the small clock view, large clock view and smartspace. */
object KeyguardPreviewClockSmartspaceViewBinder {
@JvmStatic
fun bind(
largeClockHostView: View,
smallClockHostView: View,
smartspace: View?,
viewModel: KeyguardPreviewClockSmartspaceViewModel,
) {
largeClockHostView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isLargeClockVisible.collect { largeClockHostView.isVisible = it }
}
}
smallClockHostView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.isSmallClockVisible.collect { smallClockHostView.isVisible = it }
}
}
smartspace?.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.smartSpaceTopPadding.collect { smartspace.setTopPadding(it) }
}
}
}
private fun View.setTopPadding(padding: Int) {
setPaddingRelative(paddingStart, padding, paddingEnd, paddingBottom)
}
}

View File

@@ -22,6 +22,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.res.Resources
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.os.Bundle
@@ -33,6 +34,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.FrameLayout
import androidx.core.view.isInvisible
import com.android.keyguard.ClockEventController
import com.android.keyguard.KeyguardClockSwitch
import com.android.systemui.R
@@ -40,7 +42,10 @@ import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockSmartspaceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockSmartspaceViewModel
import com.android.systemui.plugins.ClockController
import com.android.systemui.shared.clocks.ClockRegistry
import com.android.systemui.shared.clocks.DefaultClockController
import com.android.systemui.shared.clocks.shared.model.ClockPreviewConstants
@@ -60,6 +65,7 @@ constructor(
@Application private val context: Context,
@Main private val mainDispatcher: CoroutineDispatcher,
@Main private val mainHandler: Handler,
private val clockSmartspaceViewModel: KeyguardPreviewClockSmartspaceViewModel,
private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
displayManager: DisplayManager,
private val windowManager: WindowManager,
@@ -79,6 +85,7 @@ constructor(
KeyguardPreviewConstants.KEY_HIGHLIGHT_QUICK_AFFORDANCES,
false,
)
/** [shouldHideClock] here means that we never create and bind the clock views */
private val shouldHideClock: Boolean =
bundle.getBoolean(ClockPreviewConstants.KEY_HIDE_CLOCK, false)
@@ -87,7 +94,8 @@ constructor(
val surfacePackage: SurfaceControlViewHost.SurfacePackage
get() = host.surfacePackage
private var clockView: View? = null
private lateinit var largeClockHostView: FrameLayout
private lateinit var smallClockHostView: FrameLayout
private var smartSpaceView: View? = null
private var colorOverride: Int? = null
@@ -126,6 +134,12 @@ constructor(
if (!shouldHideClock) {
setUpClock(rootView)
KeyguardPreviewClockSmartspaceViewBinder.bind(
largeClockHostView,
smallClockHostView,
smartSpaceView,
clockSmartspaceViewModel,
)
}
rootView.measure(
@@ -205,11 +219,9 @@ constructor(
smartSpaceView = lockscreenSmartspaceController.buildAndConnectDateView(parentView)
val topPadding: Int =
with(context.resources) {
getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
}
KeyguardPreviewClockSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
context.resources
)
val startPadding: Int =
with(context.resources) {
@@ -284,10 +296,19 @@ constructor(
}
private fun setUpClock(parentView: ViewGroup) {
largeClockHostView = createLargeClockHostView()
largeClockHostView.isInvisible = true
parentView.addView(largeClockHostView)
smallClockHostView = createSmallClockHostView(parentView.resources)
smallClockHostView.isInvisible = true
parentView.addView(smallClockHostView)
// TODO (b/283465254): Move the listeners to KeyguardClockRepository
val clockChangeListener =
object : ClockRegistry.ClockChangeListener {
override fun onCurrentClockChanged() {
onClockChanged(parentView)
onClockChanged()
}
}
clockRegistry.registerClockChangeListener(clockChangeListener)
@@ -317,62 +338,89 @@ constructor(
disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
val layoutChangeListener =
object : View.OnLayoutChangeListener {
override fun onLayoutChange(
v: View,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int
) {
if (clockController.clock !is DefaultClockController) {
clockController.clock
?.largeClock
?.events
?.onTargetRegionChanged(
KeyguardClockSwitch.getLargeClockRegion(parentView)
)
}
View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
if (clockController.clock !is DefaultClockController) {
clockController.clock
?.largeClock
?.events
?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
}
}
parentView.addOnLayoutChangeListener(layoutChangeListener)
disposables.add(
DisposableHandle { parentView.removeOnLayoutChangeListener(layoutChangeListener) }
)
onClockChanged(parentView)
onClockChanged()
}
private fun onClockChanged(parentView: ViewGroup) {
private fun createLargeClockHostView(): FrameLayout {
val hostView = FrameLayout(context)
hostView.layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT,
)
return hostView
}
private fun createSmallClockHostView(resources: Resources): FrameLayout {
val hostView = FrameLayout(context)
val layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
resources.getDimensionPixelSize(R.dimen.small_clock_height)
)
layoutParams.topMargin =
KeyguardPreviewClockSmartspaceViewModel.getStatusBarHeight(resources) +
resources.getDimensionPixelSize(R.dimen.small_clock_padding_top)
hostView.layoutParams = layoutParams
hostView.setPaddingRelative(
resources.getDimensionPixelSize(R.dimen.clock_padding_start),
0,
0,
0
)
hostView.clipChildren = false
return hostView
}
private fun onClockChanged() {
val clock = clockRegistry.createCurrentClock()
clockController.clock = clock
colorOverride?.let { clock.events.onSeedColorChanged(it) }
clock.largeClock.events.onTargetRegionChanged(
KeyguardClockSwitch.getLargeClockRegion(parentView)
)
clockView?.let { parentView.removeView(it) }
clockView =
clock.largeClock.view.apply {
if (shouldHighlightSelectedAffordance) {
alpha = DIM_ALPHA
}
parentView.addView(this)
visibility = View.VISIBLE
}
updateLargeClock(clock)
updateSmallClock(clock)
// Hide smart space if the clock has weather display; otherwise show it
hideSmartspace(clock.largeClock.config.hasCustomWeatherDataDisplay)
}
private fun updateLargeClock(clock: ClockController) {
clock.largeClock.events.onTargetRegionChanged(
KeyguardClockSwitch.getLargeClockRegion(largeClockHostView)
)
if (shouldHighlightSelectedAffordance) {
clock.largeClock.view.alpha = DIM_ALPHA
}
largeClockHostView.removeAllViews()
largeClockHostView.addView(clock.largeClock.view)
}
private fun updateSmallClock(clock: ClockController) {
clock.smallClock.events.onTargetRegionChanged(
KeyguardClockSwitch.getSmallClockRegion(smallClockHostView)
)
if (shouldHighlightSelectedAffordance) {
clock.smallClock.view.alpha = DIM_ALPHA
}
smallClockHostView.removeAllViews()
smallClockHostView.addView(clock.smallClock.view)
}
companion object {
private const val KEY_HOST_TOKEN = "host_token"
private const val KEY_VIEW_WIDTH = "width"

View File

@@ -0,0 +1,77 @@
/*
* 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.viewmodel
import android.content.Context
import android.content.res.Resources
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.SettingsClockSize
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/** View model for the small clock view, large clock view and smartspace. */
class KeyguardPreviewClockSmartspaceViewModel
@Inject
constructor(
@Application private val context: Context,
interactor: KeyguardClockInteractor,
) {
val isLargeClockVisible: Flow<Boolean> =
interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC }
val isSmallClockVisible: Flow<Boolean> =
interactor.selectedClockSize.map { it == SettingsClockSize.SMALL }
val smartSpaceTopPadding: Flow<Int> =
interactor.selectedClockSize.map {
when (it) {
SettingsClockSize.DYNAMIC -> getLargeClockSmartspaceTopPadding(context.resources)
SettingsClockSize.SMALL -> getSmallClockSmartspaceTopPadding(context.resources)
}
}
companion object {
fun getLargeClockSmartspaceTopPadding(resources: Resources): Int {
return with(resources) {
getDimensionPixelSize(R.dimen.status_bar_header_height_keyguard) +
getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset) +
getDimensionPixelSize(R.dimen.keyguard_clock_top_margin)
}
}
fun getSmallClockSmartspaceTopPadding(resources: Resources): Int {
return with(resources) {
getStatusBarHeight(this) +
getDimensionPixelSize(R.dimen.small_clock_padding_top) +
getDimensionPixelSize(R.dimen.small_clock_height)
}
}
fun getStatusBarHeight(resource: Resources): Int {
var result = 0
val resourceId: Int = resource.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = resource.getDimensionPixelSize(resourceId)
}
return result
}
}
}