Merge "Remove app attribution UI for FSI" into udc-dev

This commit is contained in:
Lyn Han
2023-02-27 22:23:14 +00:00
committed by Android (Google) Code Review
13 changed files with 0 additions and 864 deletions

View File

@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<com.android.systemui.statusbar.notification.fsi.FsiChromeView android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="50dp"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:id="@+id/fsi_chrome"
android:layout_height="50dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/fsi_app_icon"
android:layout_width="50dp"
android:layout_height="match_parent"
android:contentDescription="@null" />
<TextView
android:id="@+id/fsi_app_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="10dp"
android:textSize="22dp"
android:gravity="center"
android:textColor="#FFFFFF"
android:text="AppName" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/fsi_fullscreen_button"
android:layout_width="100dp"
android:layout_height="match_parent"
android:text="fullscreen" />
<Button
android:id="@+id/fsi_dismiss_button"
android:layout_width="100dp"
android:layout_height="match_parent"
android:text="dismiss" />
</LinearLayout>
</com.android.systemui.statusbar.notification.fsi.FsiChromeView>

View File

@@ -257,9 +257,6 @@
<!-- Radius for notifications corners with adjacent notifications -->
<dimen name="notification_corner_radius_small">4dp</dimen>
<!-- Vertical padding of the FSI container -->
<dimen name="fsi_chrome_vertical_padding">80dp</dimen>
<!-- the padding of the shelf icon container -->
<dimen name="shelf_icon_container_padding">13dp</dimen>

View File

@@ -47,10 +47,7 @@ import com.android.systemui.reardisplay.RearDisplayDialogController
import com.android.systemui.recents.Recents
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.notification.fsi.FsiChromeViewBinder
import com.android.systemui.statusbar.phone.KeyguardLiftController
import com.android.systemui.stylus.StylusUsiPowerStartable
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -91,24 +88,6 @@ abstract class SystemUICoreStartableModule {
@ClassKey(ClipboardListener::class)
abstract fun bindClipboardListener(sysui: ClipboardListener): CoreStartable
/** Inject into FsiChromeRepo. */
@Binds
@IntoMap
@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 FsiChromeWindowBinder. */
@Binds
@IntoMap
@ClassKey(FsiChromeViewBinder::class)
abstract fun bindFsiChromeWindowBinder(sysui: FsiChromeViewBinder): CoreStartable
/** Inject into GlobalActionsComponent. */
@Binds
@IntoMap

View File

@@ -87,9 +87,6 @@ object Flags {
val NOTIFICATION_GROUP_DISMISSAL_ANIMATION =
releasedFlag(259217907, "notification_group_dismissal_animation")
// TODO(b/257506350): Tracking Bug
@JvmField val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
@JvmField
val SIMPLIFIED_APPEAR_FRACTION =
unreleasedFlag(259395680, "simplified_appear_fraction", teamfood = true)

View File

@@ -1,102 +0,0 @@
package com.android.systemui.statusbar.notification.fsi
import android.app.PendingIntent
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.os.RemoteException
import android.service.dreams.IDreamManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
import com.android.systemui.statusbar.phone.CentralSurfaces
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
/**
* Class that bridges the gap between clean app architecture and existing code. Provides new
* implementation of StatusBarNotificationActivityStarter launchFullscreenIntent that pipes
* one-directional data => FsiChromeViewModel => FsiChromeView.
*/
@SysUISingleton
class FsiChromeRepo
@Inject
constructor(
private val context: Context,
private val pm: PackageManager,
private val keyguardRepo: KeyguardRepository,
private val launchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
private val featureFlags: FeatureFlags,
private val uiBgExecutor: Executor,
private val dreamManager: IDreamManager,
private val centralSurfaces: CentralSurfaces
) : CoreStartable {
companion object {
private const val classTag = "FsiChromeRepo"
}
data class FSIInfo(
val appName: String,
val appIcon: Drawable,
val fullscreenIntent: PendingIntent
)
private val _infoFlow = MutableStateFlow<FSIInfo?>(null)
val infoFlow: StateFlow<FSIInfo?> = _infoFlow
override fun start() {
log("$classTag start listening for FSI notifications")
// Listen for FSI launch events for the lifetime of SystemUI.
launchFullScreenIntentProvider.registerListener { entry -> launchFullscreenIntent(entry) }
}
fun dismiss() {
_infoFlow.value = null
}
fun onFullscreen() {
// TODO(b/243421660) implement transition from container to fullscreen
}
fun stopScreenSaver() {
uiBgExecutor.execute {
try {
dreamManager.awaken()
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
fun launchFullscreenIntent(entry: NotificationEntry) {
if (!featureFlags.isEnabled(Flags.FSI_CHROME)) {
return
}
if (!keyguardRepo.isKeyguardShowing()) {
return
}
stopScreenSaver()
var appName = pm.getApplicationLabel(context.applicationInfo) as String
val appIcon = pm.getApplicationIcon(context.packageName)
val fullscreenIntent = entry.sbn.notification.fullScreenIntent
log("FsiChromeRepo launchFullscreenIntent appName=$appName appIcon $appIcon")
_infoFlow.value = FSIInfo(appName, appIcon, fullscreenIntent)
// If screen is off or we're showing AOD, show lockscreen.
centralSurfaces.wakeUpForFullScreenIntent()
// Don't show HUN since we're already showing FSI.
entry.notifyFullScreenIntentLaunched()
}
}

View File

@@ -1,83 +0,0 @@
package com.android.systemui.statusbar.notification.fsi
import android.content.Context
import android.graphics.Color
import android.graphics.Color.DKGRAY
import android.graphics.Outline
import android.util.AttributeSet
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
@SysUISingleton
class FsiChromeView
@JvmOverloads
constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
companion object {
private const val classTag = "FsiChromeView"
}
lateinit var chromeContainer: LinearLayout
lateinit var appIconImageView: ImageView
lateinit var appNameTextView: TextView
lateinit var dismissButton: Button
lateinit var fullscreenButton: Button
private val cornerRadius: Float =
resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
private val vertPadding: Int =
resources.getDimensionPixelSize(R.dimen.fsi_chrome_vertical_padding)
private val sidePadding: Int =
resources.getDimensionPixelSize(R.dimen.notification_side_paddings)
init {
log("$classTag init")
}
override fun onFinishInflate() {
log("$classTag onFinishInflate")
super.onFinishInflate()
setBackgroundColor(Color.TRANSPARENT)
setPadding(
sidePadding,
vertPadding,
sidePadding,
vertPadding
) // Make smaller than fullscreen.
chromeContainer = findViewById(R.id.fsi_chrome)
chromeContainer.setBackgroundColor(DKGRAY)
appIconImageView = findViewById(R.id.fsi_app_icon)
appNameTextView = findViewById(R.id.fsi_app_name)
dismissButton = findViewById(R.id.fsi_dismiss_button)
fullscreenButton = findViewById(R.id.fsi_fullscreen_button)
outlineProvider =
object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(
/* left */ sidePadding,
/* top */ vertPadding,
/* right */ view.width - sidePadding,
/* bottom */ view.height - vertPadding,
cornerRadius
)
}
}
clipToOutline = true
}
}

View File

@@ -1,99 +0,0 @@
package com.android.systemui.statusbar.notification.fsi
import android.content.Context
import android.view.LayoutInflater
import android.view.WindowManager
import com.android.systemui.CoreStartable
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
import com.android.systemui.statusbar.phone.CentralSurfaces
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.util.concurrent.Executor
import javax.inject.Inject
@SysUISingleton
class FsiChromeViewBinder
@Inject
constructor(
val context: Context,
val windowManager: WindowManager,
val viewModelFactory: FsiChromeViewModelFactory,
val layoutInflater: LayoutInflater,
val centralSurfaces: CentralSurfaces,
@Main val mainExecutor: Executor,
@Application val scope: CoroutineScope,
) : CoreStartable {
companion object {
private const val classTag = "FsiChromeViewBinder"
}
private val fsiChromeView =
layoutInflater.inflate(R.layout.fsi_chrome_view, null /* root */, false /* attachToRoot */)
as FsiChromeView
var addedToWindowManager = false
var cornerRadius: Int = context.resources.getDimensionPixelSize(
R.dimen.notification_corner_radius)
override fun start() {
val methodTag = "start"
log("$classTag $methodTag ")
scope.launch {
log("$classTag $methodTag launch ")
viewModelFactory.viewModelFlow.collect { vm -> updateForViewModel(vm) }
}
}
private fun updateForViewModel(vm: FsiChromeViewModel?) {
val methodTag = "updateForViewModel"
if (vm == null) {
log("$classTag $methodTag viewModel is null, removing from window manager")
if (addedToWindowManager) {
windowManager.removeView(fsiChromeView)
addedToWindowManager = false
}
return
}
bindViewModel(vm, windowManager)
if (addedToWindowManager) {
log("$classTag $methodTag already addedToWindowManager")
} else {
windowManager.addView(fsiChromeView, FsiTaskViewConfig.getWmLayoutParams("PackageName"))
addedToWindowManager = true
}
}
private fun bindViewModel(
vm: FsiChromeViewModel,
windowManager: WindowManager,
) {
log("$classTag bindViewModel")
fsiChromeView.appIconImageView.setImageDrawable(vm.appIcon)
fsiChromeView.appNameTextView.text = vm.appName
fsiChromeView.dismissButton.setOnClickListener { vm.onDismiss() }
fsiChromeView.fullscreenButton.setOnClickListener { vm.onFullscreen() }
vm.taskView.cornerRadius = cornerRadius.toFloat()
vm.taskView.startActivity(
vm.fsi,
FsiTaskViewConfig.getFillInIntent(),
FsiTaskViewConfig.getActivityOptions(context, windowManager),
FsiTaskViewConfig.getLaunchBounds(windowManager)
)
log("$classTag bindViewModel started taskview activity")
fsiChromeView.addView(vm.taskView)
}
}

View File

@@ -1,87 +0,0 @@
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()
}
}

View File

@@ -1,16 +0,0 @@
package com.android.systemui.statusbar.notification.fsi
class FsiDebug {
companion object {
private const val debugTag = "FsiDebug"
private const val debug = true
fun log(s: Any) {
if (!debug) {
return
}
android.util.Log.d(debugTag, "$s")
}
}
}

View File

@@ -1,75 +0,0 @@
package com.android.systemui.statusbar.notification.fsi
import android.app.ActivityOptions
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.graphics.Rect
import android.os.Binder
import android.view.ViewGroup
import android.view.WindowManager
/**
* Config for adding the FsiChromeView window to WindowManager and starting the FSI activity.
*/
class FsiTaskViewConfig {
companion object {
private const val classTag = "FsiTaskViewConfig"
fun getWmLayoutParams(packageName: String): WindowManager.LayoutParams {
val params: WindowManager.LayoutParams?
params =
WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED or
WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER,
PixelFormat.TRANSLUCENT
)
params.setTrustedOverlay()
params.fitInsetsTypes = 0
params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
params.token = Binder()
params.packageName = packageName
params.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
params.privateFlags =
params.privateFlags or WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
return params
}
fun getFillInIntent(): Intent {
val fillInIntent = Intent()
fillInIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
// FLAG_ACTIVITY_NEW_TASK is auto-applied because
// we're starting the FSI activity from a non-Activity context
return fillInIntent
}
fun getLaunchBounds(windowManager: WindowManager): Rect {
// TODO(b/243421660) check this works for non-resizeable activity
return Rect()
}
fun getActivityOptions(context: Context, windowManager: WindowManager): ActivityOptions {
// Custom options so there is no activity transition animation
val options =
ActivityOptions.makeCustomAnimation(context, 0 /* enterResId */, 0 /* exitResId */)
options.taskAlwaysOnTop = true
options.pendingIntentLaunchFlags =
Intent.FLAG_ACTIVITY_NEW_DOCUMENT or
Intent.FLAG_ACTIVITY_MULTIPLE_TASK or
Intent.FLAG_ACTIVITY_NEW_TASK
options.launchBounds = getLaunchBounds(windowManager)
return options
}
}
}

View File

@@ -561,10 +561,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
mLogger.logFullScreenIntentSuppressedByVR(entry);
return;
}
if (mFeatureFlags.isEnabled(Flags.FSI_CHROME)) {
// FsiChromeRepo runs its own implementation of launchFullScreenIntent
return;
}
// Stop screensaver if the notification has a fullscreen intent.
// (like an incoming phone call)
mUiBgExecutor.execute(() -> {

View File

@@ -1,210 +0,0 @@
/*
* 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.R
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.os.UserHandle
import android.service.dreams.IDreamManager
import android.service.notification.StatusBarNotification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
import com.android.systemui.statusbar.phone.CentralSurfaces
import java.util.concurrent.Executor
import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
class FsiChromeRepoTest : SysuiTestCase() {
@Mock lateinit var centralSurfaces: CentralSurfaces
@Mock lateinit var fsiChromeRepo: FsiChromeRepo
@Mock lateinit var packageManager: PackageManager
var keyguardRepo = FakeKeyguardRepository()
@Mock private lateinit var applicationInfo: ApplicationInfo
@Mock lateinit var launchFullScreenIntentProvider: LaunchFullScreenIntentProvider
var featureFlags = FakeFeatureFlags()
@Mock lateinit var dreamManager: IDreamManager
// Execute all foreground & background requests immediately
private val uiBgExecutor = Executor { r -> r.run() }
private val appName: String = "appName"
private val appIcon: Drawable = context.getDrawable(com.android.systemui.R.drawable.ic_android)
private val fsi: PendingIntent = Mockito.mock(PendingIntent::class.java)
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
// Set up package manager mocks
whenever(packageManager.getApplicationIcon(anyString())).thenReturn(appIcon)
whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java)))
.thenReturn(appIcon)
whenever(packageManager.getApplicationLabel(any())).thenReturn(appName)
mContext.setMockPackageManager(packageManager)
fsiChromeRepo =
FsiChromeRepo(
mContext,
packageManager,
keyguardRepo,
launchFullScreenIntentProvider,
featureFlags,
uiBgExecutor,
dreamManager,
centralSurfaces
)
}
private fun createFsiEntry(fsi: PendingIntent): NotificationEntry {
val nb =
Notification.Builder(mContext, "a")
.setContentTitle("foo")
.setSmallIcon(R.drawable.sym_def_app_icon)
.setFullScreenIntent(fsi, /* highPriority= */ true)
val sbn =
StatusBarNotification(
"pkg",
"opPkg",
/* id= */ 0,
"tag" + System.currentTimeMillis(),
/* uid= */ 0,
/* initialPid */ 0,
nb.build(),
UserHandle(0),
/* overrideGroupKey= */ null,
/* postTime= */ 0
)
val entry = Mockito.mock(NotificationEntry::class.java)
whenever(entry.importance).thenReturn(NotificationManager.IMPORTANCE_HIGH)
whenever(entry.sbn).thenReturn(sbn)
return entry
}
@Test
fun testLaunchFullscreenIntent_flagNotEnabled_noLaunch() {
// Setup
featureFlags.set(Flags.FSI_CHROME, false)
// Test
val entry = createFsiEntry(fsi)
fsiChromeRepo.launchFullscreenIntent(entry)
// Verify
Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent()
}
@Test
fun testLaunchFullscreenIntent_notOnKeyguard_noLaunch() {
// Setup
featureFlags.set(Flags.FSI_CHROME, true)
keyguardRepo.setKeyguardShowing(false)
// Test
val entry = createFsiEntry(fsi)
fsiChromeRepo.launchFullscreenIntent(entry)
// Verify
Mockito.verify(centralSurfaces, never()).wakeUpForFullScreenIntent()
}
@Test
fun testLaunchFullscreenIntent_stopsScreensaver() {
// Setup
featureFlags.set(Flags.FSI_CHROME, true)
keyguardRepo.setKeyguardShowing(true)
// Test
val entry = createFsiEntry(fsi)
fsiChromeRepo.launchFullscreenIntent(entry)
// Verify
Mockito.verify(dreamManager, times(1)).awaken()
}
@Test
fun testLaunchFullscreenIntent_updatesFsiInfoFlow() {
// Setup
featureFlags.set(Flags.FSI_CHROME, true)
keyguardRepo.setKeyguardShowing(true)
// Test
val entry = createFsiEntry(fsi)
fsiChromeRepo.launchFullscreenIntent(entry)
// Verify
val expectedFsiInfo = FsiChromeRepo.FSIInfo(appName, appIcon, fsi)
assertEquals(expectedFsiInfo, fsiChromeRepo.infoFlow.value)
}
@Test
fun testLaunchFullscreenIntent_notifyFsiLaunched() {
// Setup
featureFlags.set(Flags.FSI_CHROME, true)
keyguardRepo.setKeyguardShowing(true)
// Test
val entry = createFsiEntry(fsi)
fsiChromeRepo.launchFullscreenIntent(entry)
// Verify
Mockito.verify(entry, times(1)).notifyFullScreenIntentLaunched()
}
@Test
fun testLaunchFullscreenIntent_wakesUpDevice() {
// Setup
featureFlags.set(Flags.FSI_CHROME, true)
keyguardRepo.setKeyguardShowing(true)
// Test
val entry = createFsiEntry(fsi)
fsiChromeRepo.launchFullscreenIntent(entry)
// Verify
Mockito.verify(centralSurfaces, times(1)).wakeUpForFullScreenIntent()
}
}

View File

@@ -1,112 +0,0 @@
/*
* 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)
}
}
}