Support small clock for the lock screen preview

Added small clock on the lock screen preview.
When settings is dynamic clock, show large.
When settings is small clock, show small.

Test: See bug. Manually tested.
Bug: 274927017
Change-Id: I8212b07f35bd1fe687f97665b3968b834b5d86b3
This commit is contained in:
George Lin
2023-05-17 21:45:41 +00:00
parent 255c2994b3
commit b8fff05e30
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
}
}
}