Merge "Add FSI view model" into tm-qpr-dev am: b2433a64cc
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20694074 Change-Id: Ia6e0104a2f625371198668750206840f0b74435d Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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.shortcut.ShortcutKeyDispatcher
|
||||||
import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo
|
import com.android.systemui.statusbar.notification.fsi.FsiChromeRepo
|
||||||
import com.android.systemui.statusbar.notification.InstantAppNotifier
|
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.statusbar.phone.KeyguardLiftController
|
||||||
import com.android.systemui.stylus.StylusUsiPowerStartable
|
import com.android.systemui.stylus.StylusUsiPowerStartable
|
||||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
||||||
@@ -86,6 +87,12 @@ abstract class SystemUICoreStartableModule {
|
|||||||
@ClassKey(FsiChromeRepo::class)
|
@ClassKey(FsiChromeRepo::class)
|
||||||
abstract fun bindFSIChromeRepo(sysui: FsiChromeRepo): CoreStartable
|
abstract fun bindFSIChromeRepo(sysui: FsiChromeRepo): CoreStartable
|
||||||
|
|
||||||
|
/** Inject into FsiChromeWindowViewModel. */
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(FsiChromeViewModelFactory::class)
|
||||||
|
abstract fun bindFSIChromeWindowViewModel(sysui: FsiChromeViewModelFactory): CoreStartable
|
||||||
|
|
||||||
/** Inject into GarbageMonitor.Service. */
|
/** Inject into GarbageMonitor.Service. */
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@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