Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt 0 → 100644 +85 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ @file:OptIn(ExperimentalCoroutinesApi::class) package com.android.systemui.statusbar.notification.row.ui.viewmodel import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel import com.android.systemui.statusbar.notification.row.shared.IconModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) @SmallTest @EnableFlags(RichOngoingNotificationFlag.FLAG_NAME) class EnRouteViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val repository = kosmos.fakeNotificationRowRepository private var contentModel: EnRouteContentModel? get() = repository.richOngoingContentModel.value as? EnRouteContentModel set(value) { repository.richOngoingContentModel.value = value } private lateinit var underTest: EnRouteViewModel @Before fun setup() { underTest = kosmos.getEnRouteViewModel(repository) } @Test fun viewModelShowsContent() = testScope.runTest { val title by collectLastValue(underTest.title) val text by collectLastValue(underTest.text) contentModel = exampleEnRouteContent( title = "Example EnRoute Title", text = "Example EnRoute Text", ) assertThat(title).isEqualTo("Example EnRoute Title") assertThat(text).isEqualTo("Example EnRoute Text") } private fun exampleEnRouteContent( icon: IconModel = mock(), title: CharSequence = "example text", text: CharSequence = "example title", ) = EnRouteContentModel( smallIcon = icon, title = title, text = text, ) } packages/SystemUI/res/layout/notification_template_en_route_contracted.xml 0 → 100644 +29 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2024 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. --> <com.android.systemui.statusbar.notification.row.ui.view.EnRouteView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/status_bar_latest_event_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:minHeight="@*android:dimen/notification_headerless_min_height" android:tag="enroute" > <include layout="@*android:layout/notification_template_material_base" /> </com.android.systemui.statusbar.notification.row.ui.view.EnRouteView> No newline at end of file packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt +22 −6 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.Context import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel import com.android.systemui.statusbar.notification.row.shared.IconModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag Loading Loading @@ -68,12 +69,13 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : builder: Notification.Builder, systemUIContext: Context, packageContext: Context ): RichOngoingContentModel? = try { ): RichOngoingContentModel? { val sbn = entry.sbn val notification = sbn.notification val icon = IconModel(notification.smallIcon) if (sbn.packageName == "com.google.android.deskclock") { try { return if (sbn.packageName == "com.google.android.deskclock") { when (notification.channelId) { "Timers v2" -> { parseTimerNotification(notification, icon) Loading @@ -87,10 +89,13 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : null } } } else if (builder.style is Notification.EnRouteStyle) { parseEnRouteNotification(notification, icon) } else null } catch (e: Exception) { Log.e("RONs", "Error parsing RON", e) null return null } } /** Loading Loading @@ -199,4 +204,15 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : .plusMinutes(minute.toLong()) .plusSeconds(second.toLong()) } private fun parseEnRouteNotification( notification: Notification, icon: IconModel, ): EnRouteContentModel { return EnRouteContentModel( smallIcon = icon, title = notification.extras.getCharSequence(Notification.EXTRA_TITLE), text = notification.extras.getCharSequence(Notification.EXTRA_TEXT), ) } } packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt +49 −3 Original line number Diff line number Diff line Loading @@ -27,12 +27,15 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag import com.android.systemui.statusbar.notification.row.shared.StopwatchContentModel import com.android.systemui.statusbar.notification.row.shared.TimerContentModel import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView import com.android.systemui.statusbar.notification.row.ui.view.TimerView import com.android.systemui.statusbar.notification.row.ui.viewbinder.EnRouteViewBinder import com.android.systemui.statusbar.notification.row.ui.viewbinder.TimerViewBinder import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel import javax.inject.Inject Loading Loading @@ -119,7 +122,15 @@ constructor( parentView, viewType ) is StopwatchContentModel -> TODO("Not yet implemented") is EnRouteContentModel -> inflateEnRouteView( existingView, component::createEnRouteViewModel, systemUiContext, parentView, viewType ) else -> TODO("Not yet implemented") } } Loading @@ -131,7 +142,8 @@ constructor( if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false return when (contentModel) { is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType) is StopwatchContentModel -> TODO("Not yet implemented") is EnRouteContentModel -> canKeepEnRouteView(contentModel, existingView, viewType) else -> TODO("Not yet implemented") } } Loading Loading @@ -167,4 +179,38 @@ constructor( existingView: View?, viewType: RichOngoingNotificationViewType ): Boolean = true private fun inflateEnRouteView( existingView: View?, createViewModel: () -> EnRouteViewModel, systemUiContext: Context, parentView: ViewGroup, viewType: RichOngoingNotificationViewType, ): ContentViewInflationResult { if (existingView is EnRouteView && !existingView.isReinflateNeeded()) return KeepExistingView return when (viewType) { RichOngoingNotificationViewType.Contracted -> { val newView = LayoutInflater.from(systemUiContext) .inflate( R.layout.notification_template_en_route_contracted, parentView, /* attachToRoot= */ false ) as EnRouteView InflatedContentViewHolder(newView) { EnRouteViewBinder.bindWhileAttached(newView, createViewModel()) } } RichOngoingNotificationViewType.Expanded, RichOngoingNotificationViewType.HeadsUp -> NullContentView } } private fun canKeepEnRouteView( contentModel: EnRouteContentModel, existingView: View?, viewType: RichOngoingNotificationViewType ): Boolean = true } packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt +5 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row.domain.interactor import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel import com.android.systemui.statusbar.notification.row.shared.TimerContentModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow Loading @@ -26,4 +27,8 @@ class NotificationRowInteractor @Inject constructor(repository: NotificationRowR /** Content of a rich ongoing timer notification. */ val timerContentModel: Flow<TimerContentModel> = repository.richOngoingContentModel.filterIsInstance<TimerContentModel>() /** Content of a rich ongoing timer notification. */ val enRouteContentModel: Flow<EnRouteContentModel> = repository.richOngoingContentModel.filterIsInstance<EnRouteContentModel>() } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/EnRouteViewModelTest.kt 0 → 100644 +85 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ @file:OptIn(ExperimentalCoroutinesApi::class) package com.android.systemui.statusbar.notification.row.ui.viewmodel import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.notification.row.data.repository.fakeNotificationRowRepository import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel import com.android.systemui.statusbar.notification.row.shared.IconModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) @SmallTest @EnableFlags(RichOngoingNotificationFlag.FLAG_NAME) class EnRouteViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val repository = kosmos.fakeNotificationRowRepository private var contentModel: EnRouteContentModel? get() = repository.richOngoingContentModel.value as? EnRouteContentModel set(value) { repository.richOngoingContentModel.value = value } private lateinit var underTest: EnRouteViewModel @Before fun setup() { underTest = kosmos.getEnRouteViewModel(repository) } @Test fun viewModelShowsContent() = testScope.runTest { val title by collectLastValue(underTest.title) val text by collectLastValue(underTest.text) contentModel = exampleEnRouteContent( title = "Example EnRoute Title", text = "Example EnRoute Text", ) assertThat(title).isEqualTo("Example EnRoute Title") assertThat(text).isEqualTo("Example EnRoute Text") } private fun exampleEnRouteContent( icon: IconModel = mock(), title: CharSequence = "example text", text: CharSequence = "example title", ) = EnRouteContentModel( smallIcon = icon, title = title, text = text, ) }
packages/SystemUI/res/layout/notification_template_en_route_contracted.xml 0 → 100644 +29 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (C) 2024 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. --> <com.android.systemui.statusbar.notification.row.ui.view.EnRouteView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/status_bar_latest_event_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:minHeight="@*android:dimen/notification_headerless_min_height" android:tag="enroute" > <include layout="@*android:layout/notification_template_material_base" /> </com.android.systemui.statusbar.notification.row.ui.view.EnRouteView> No newline at end of file
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationContentExtractor.kt +22 −6 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.content.Context import android.util.Log import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel import com.android.systemui.statusbar.notification.row.shared.IconModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag Loading Loading @@ -68,12 +69,13 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : builder: Notification.Builder, systemUIContext: Context, packageContext: Context ): RichOngoingContentModel? = try { ): RichOngoingContentModel? { val sbn = entry.sbn val notification = sbn.notification val icon = IconModel(notification.smallIcon) if (sbn.packageName == "com.google.android.deskclock") { try { return if (sbn.packageName == "com.google.android.deskclock") { when (notification.channelId) { "Timers v2" -> { parseTimerNotification(notification, icon) Loading @@ -87,10 +89,13 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : null } } } else if (builder.style is Notification.EnRouteStyle) { parseEnRouteNotification(notification, icon) } else null } catch (e: Exception) { Log.e("RONs", "Error parsing RON", e) null return null } } /** Loading Loading @@ -199,4 +204,15 @@ class RichOngoingNotificationContentExtractorImpl @Inject constructor() : .plusMinutes(minute.toLong()) .plusSeconds(second.toLong()) } private fun parseEnRouteNotification( notification: Notification, icon: IconModel, ): EnRouteContentModel { return EnRouteContentModel( smallIcon = icon, title = notification.extras.getCharSequence(Notification.EXTRA_TITLE), text = notification.extras.getCharSequence(Notification.EXTRA_TEXT), ) } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RichOngoingNotificationViewInflater.kt +49 −3 Original line number Diff line number Diff line Loading @@ -27,12 +27,15 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.InflatedContentViewHolder import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.KeepExistingView import com.android.systemui.statusbar.notification.row.ContentViewInflationResult.NullContentView import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingContentModel import com.android.systemui.statusbar.notification.row.shared.RichOngoingNotificationFlag import com.android.systemui.statusbar.notification.row.shared.StopwatchContentModel import com.android.systemui.statusbar.notification.row.shared.TimerContentModel import com.android.systemui.statusbar.notification.row.ui.view.EnRouteView import com.android.systemui.statusbar.notification.row.ui.view.TimerView import com.android.systemui.statusbar.notification.row.ui.viewbinder.EnRouteViewBinder import com.android.systemui.statusbar.notification.row.ui.viewbinder.TimerViewBinder import com.android.systemui.statusbar.notification.row.ui.viewmodel.EnRouteViewModel import com.android.systemui.statusbar.notification.row.ui.viewmodel.RichOngoingViewModelComponent import com.android.systemui.statusbar.notification.row.ui.viewmodel.TimerViewModel import javax.inject.Inject Loading Loading @@ -119,7 +122,15 @@ constructor( parentView, viewType ) is StopwatchContentModel -> TODO("Not yet implemented") is EnRouteContentModel -> inflateEnRouteView( existingView, component::createEnRouteViewModel, systemUiContext, parentView, viewType ) else -> TODO("Not yet implemented") } } Loading @@ -131,7 +142,8 @@ constructor( if (RichOngoingNotificationFlag.isUnexpectedlyInLegacyMode()) return false return when (contentModel) { is TimerContentModel -> canKeepTimerView(contentModel, existingView, viewType) is StopwatchContentModel -> TODO("Not yet implemented") is EnRouteContentModel -> canKeepEnRouteView(contentModel, existingView, viewType) else -> TODO("Not yet implemented") } } Loading Loading @@ -167,4 +179,38 @@ constructor( existingView: View?, viewType: RichOngoingNotificationViewType ): Boolean = true private fun inflateEnRouteView( existingView: View?, createViewModel: () -> EnRouteViewModel, systemUiContext: Context, parentView: ViewGroup, viewType: RichOngoingNotificationViewType, ): ContentViewInflationResult { if (existingView is EnRouteView && !existingView.isReinflateNeeded()) return KeepExistingView return when (viewType) { RichOngoingNotificationViewType.Contracted -> { val newView = LayoutInflater.from(systemUiContext) .inflate( R.layout.notification_template_en_route_contracted, parentView, /* attachToRoot= */ false ) as EnRouteView InflatedContentViewHolder(newView) { EnRouteViewBinder.bindWhileAttached(newView, createViewModel()) } } RichOngoingNotificationViewType.Expanded, RichOngoingNotificationViewType.HeadsUp -> NullContentView } } private fun canKeepEnRouteView( contentModel: EnRouteContentModel, existingView: View?, viewType: RichOngoingNotificationViewType ): Boolean = true }
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/domain/interactor/NotificationRowInteractor.kt +5 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row.domain.interactor import com.android.systemui.statusbar.notification.row.data.repository.NotificationRowRepository import com.android.systemui.statusbar.notification.row.shared.EnRouteContentModel import com.android.systemui.statusbar.notification.row.shared.TimerContentModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow Loading @@ -26,4 +27,8 @@ class NotificationRowInteractor @Inject constructor(repository: NotificationRowR /** Content of a rich ongoing timer notification. */ val timerContentModel: Flow<TimerContentModel> = repository.richOngoingContentModel.filterIsInstance<TimerContentModel>() /** Content of a rich ongoing timer notification. */ val enRouteContentModel: Flow<EnRouteContentModel> = repository.richOngoingContentModel.filterIsInstance<EnRouteContentModel>() }