Remove app attribution UI for FSI
Bug: 243421660 Fixes: 257506350 Test: compiles Change-Id: Icb1aa3a417c93d196a5fd401919191cefbb76fa4
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(() -> {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user