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 + } + } +}