From b8fff05e30ccab6048a4d6f09b736d22ccf891db Mon Sep 17 00:00:00 2001 From: George Lin Date: Wed, 17 May 2023 21:45:41 +0000 Subject: [PATCH] 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 --- .../SystemUI/res-keyguard/values/dimens.xml | 1 + .../android/keyguard/KeyguardClockSwitch.java | 20 ++- .../repository/KeyguardClockRepository.kt | 65 +++++++++ .../interactor/KeyguardClockInteractor.kt | 34 +++++ .../shared/model/SettingsClockSize.kt | 23 +++ ...eyguardPreviewClockSmartspaceViewBinder.kt | 60 ++++++++ .../ui/preview/KeyguardPreviewRenderer.kt | 136 ++++++++++++------ ...KeyguardPreviewClockSmartspaceViewModel.kt | 77 ++++++++++ 8 files changed, 365 insertions(+), 51 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index cad2c162a589a..4b79689534204 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -96,6 +96,7 @@ 114dp + 28dp 28dp 32dp 16dp diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index e54d4739dc970..432153b6726a1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -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()) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt new file mode 100644 index 0000000000000..641e20b4a3fc0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepository.kt @@ -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 = + 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 + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt new file mode 100644 index 0000000000000..98f445c4419ab --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt @@ -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 = repository.selectedClockSize +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt new file mode 100644 index 0000000000000..c6b0f58a0cd37 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/SettingsClockSize.kt @@ -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, +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt new file mode 100644 index 0000000000000..57c32b3a56d94 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockSmartspaceViewBinder.kt @@ -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) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index 555a09baa5b78..4308d843c27a8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -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" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt new file mode 100644 index 0000000000000..00c603b04ccf4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardPreviewClockSmartspaceViewModel.kt @@ -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 = + interactor.selectedClockSize.map { it == SettingsClockSize.DYNAMIC } + + val isSmallClockVisible: Flow = + interactor.selectedClockSize.map { it == SettingsClockSize.SMALL } + + val smartSpaceTopPadding: Flow = + 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 + } + } +}