Add FSI view model
Bug: 243421660 Test: FsiChromeWindowViewModelTest Change-Id: I2964d4df7985271d304324602a3e846f45bad7b0
This commit is contained in:
@@ -42,6 +42,7 @@ import com.android.systemui.settings.dagger.MultiUserUtilsModule
|
||||
import com.android.systemui.shortcut.ShortcutKeyDispatcher
|
||||
import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo
|
||||
import com.android.systemui.statusbar.notification.InstantAppNotifier
|
||||
import com.android.systemui.statusbar.notification.fsi.FsiChromeViewModelFactory
|
||||
import com.android.systemui.statusbar.phone.KeyguardLiftController
|
||||
import com.android.systemui.stylus.StylusUsiPowerStartable
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
||||
@@ -86,6 +87,12 @@ abstract class SystemUICoreStartableModule {
|
||||
@ClassKey(FsiChromeRepo::class)
|
||||
abstract fun bindFSIChromeRepo(sysui: FsiChromeRepo): CoreStartable
|
||||
|
||||
/** Inject into FsiChromeWindowViewModel. */
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(FsiChromeViewModelFactory::class)
|
||||
abstract fun bindFSIChromeWindowViewModel(sysui: FsiChromeViewModelFactory): CoreStartable
|
||||
|
||||
/** Inject into GarbageMonitor.Service. */
|
||||
@Binds
|
||||
@IntoMap
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.android.systemui.statusbar.notification.fsi
|
||||
|
||||
import android.annotation.UiContext
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.android.systemui.CoreStartable
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
|
||||
import com.android.wm.shell.TaskView
|
||||
import com.android.wm.shell.TaskViewFactory
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
|
||||
/**
|
||||
* Handle view-related data for fullscreen intent container on lockscreen. Wraps FsiChromeRepo,
|
||||
* transforms events/state into view-relevant representation for FsiChromeView. Alive for lifetime
|
||||
* of SystemUI.
|
||||
*/
|
||||
@SysUISingleton
|
||||
class FsiChromeViewModelFactory
|
||||
@Inject
|
||||
constructor(
|
||||
val repo: FsiChromeRepo,
|
||||
val taskViewFactory: Optional<TaskViewFactory>,
|
||||
@UiContext val context: Context,
|
||||
@Main val mainExecutor: Executor,
|
||||
) : CoreStartable {
|
||||
|
||||
companion object {
|
||||
private const val classTag = "FsiChromeViewModelFactory"
|
||||
}
|
||||
|
||||
val viewModelFlow: Flow<FsiChromeViewModel?> =
|
||||
repo.infoFlow.mapLatest { fsiInfo ->
|
||||
fsiInfo?.let {
|
||||
log("$classTag viewModelFlow got new fsiInfo")
|
||||
|
||||
// mapLatest emits null when FSIInfo is null
|
||||
FsiChromeViewModel(
|
||||
fsiInfo.appName,
|
||||
fsiInfo.appIcon,
|
||||
createTaskView(),
|
||||
fsiInfo.fullscreenIntent,
|
||||
repo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
log("$classTag start")
|
||||
}
|
||||
|
||||
private suspend fun createTaskView(): TaskView = suspendCancellableCoroutine { k ->
|
||||
log("$classTag createTaskView")
|
||||
|
||||
taskViewFactory.get().create(context, mainExecutor) { taskView -> k.resume(taskView) }
|
||||
}
|
||||
}
|
||||
|
||||
// Alive for lifetime of FSI.
|
||||
data class FsiChromeViewModel(
|
||||
val appName: String,
|
||||
val appIcon: Drawable,
|
||||
val taskView: TaskView,
|
||||
val fsi: PendingIntent,
|
||||
val repo: FsiChromeRepo
|
||||
) {
|
||||
companion object {
|
||||
private const val classTag = "FsiChromeViewModel"
|
||||
}
|
||||
|
||||
fun onDismiss() {
|
||||
log("$classTag onDismiss")
|
||||
repo.dismiss()
|
||||
}
|
||||
fun onFullscreen() {
|
||||
log("$classTag onFullscreen")
|
||||
repo.onFullscreen()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.statusbar.notification.fsi
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper.RunWithLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.android.wm.shell.TaskView
|
||||
import com.android.wm.shell.TaskViewFactory
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.util.Optional
|
||||
import java.util.function.Consumer
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@RunWithLooper(setAsMainLooper = true)
|
||||
class FsiChromeViewModelFactoryTest : SysuiTestCase() {
|
||||
@Mock private lateinit var taskViewFactoryOptional: Optional<TaskViewFactory>
|
||||
@Mock private lateinit var taskViewFactory: TaskViewFactory
|
||||
@Mock lateinit var taskView: TaskView
|
||||
|
||||
@Main var mainExecutor = FakeExecutor(FakeSystemClock())
|
||||
lateinit var viewModelFactory: FsiChromeViewModelFactory
|
||||
|
||||
private val fakeInfoFlow = MutableStateFlow<FsiChromeRepo.FSIInfo?>(null)
|
||||
private var fsiChromeRepo: FsiChromeRepo =
|
||||
mock<FsiChromeRepo>().apply { whenever(infoFlow).thenReturn(fakeInfoFlow) }
|
||||
|
||||
private val appName = "appName"
|
||||
private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android)
|
||||
private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java)
|
||||
private val fsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
|
||||
whenever(taskViewFactoryOptional.get()).thenReturn(taskViewFactory)
|
||||
|
||||
viewModelFactory =
|
||||
FsiChromeViewModelFactory(fsiChromeRepo, taskViewFactoryOptional, context, mainExecutor)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testViewModelFlow_update_createsTaskView() {
|
||||
runTest {
|
||||
val latestViewModel =
|
||||
viewModelFactory.viewModelFlow
|
||||
.onStart { FsiDebug.log("viewModelFactory.viewModelFlow.onStart") }
|
||||
.stateIn(
|
||||
backgroundScope, // stateIn runs forever, don't count it as test coroutine
|
||||
SharingStarted.Eagerly,
|
||||
null
|
||||
)
|
||||
runCurrent() // Drain queued backgroundScope operations
|
||||
|
||||
// Test: emit the fake FSIInfo
|
||||
fakeInfoFlow.emit(fsiInfo)
|
||||
runCurrent()
|
||||
|
||||
val taskViewFactoryCallback: Consumer<TaskView> = withArgCaptor {
|
||||
verify(taskViewFactory).create(any(), any(), capture())
|
||||
}
|
||||
taskViewFactoryCallback.accept(taskView) // this will call k.resume
|
||||
runCurrent()
|
||||
|
||||
// Verify that the factory has produced a new ViewModel
|
||||
// containing the relevant data from FsiInfo
|
||||
val expectedViewModel =
|
||||
FsiChromeViewModel(appName, appIcon, taskView, fsi, fsiChromeRepo)
|
||||
|
||||
assertThat(latestViewModel.value).isEqualTo(expectedViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user