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

Commit ee4743d1 authored by Xiaowen Lei's avatar Xiaowen Lei Committed by Android (Google) Code Review
Browse files

Merge "[RON] Scaffolding for EnRouteStyle content extraction and view inflation." into main

parents b79820a8 e0612f97
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