Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e0612f97 authored by Xiaowen Lei's avatar Xiaowen Lei
Browse files

[RON] Scaffolding for EnRouteStyle content extraction and view inflation.

For collapsed (contracted) view, wrapped
@*android:layout/notification_template_material_base inside EnRouteView
in the SystemUI layout.

For scaffolding, just filled out the minimal three fields for the
collapsed view. The rest will be filled when the structure is ok.

Flag: android.app.api_rich_ongoing
Flag: com.android.systemui.notification_row_content_binder_refactor
Bug: 359128724
Bug: 359920094
Test: EnRouteViewModelTest
Test: Trigger example EnRouteStyle notification on device
Change-Id: I6f1f5e9524768f285597e772d6ceaf885bd640d6
parent 7477c179
Loading
Loading
Loading
Loading
+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,
        )
}
+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
+22 −6
Original line number Diff line number Diff line
@@ -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
@@ -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)
@@ -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
        }
    }

    /**
@@ -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),
        )
    }
}
+49 −3
Original line number Diff line number Diff line
@@ -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
@@ -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")
        }
    }

@@ -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")
        }
    }

@@ -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
}
+5 −0
Original line number Diff line number Diff line
@@ -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
@@ -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